Skip to content

Commit a22b37b

Browse files
authored
Merge pull request #382 from wordpress-mobile/add/ui-text-formatting-tests
Add UI Tests for Simple Text Formatting
2 parents 2e987a6 + 3f26f95 commit a22b37b

File tree

4 files changed

+531
-0
lines changed

4 files changed

+531
-0
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package org.wordpress.aztec.demo;
2+
3+
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
4+
import static org.hamcrest.Matchers.allOf;
5+
6+
import android.support.test.espresso.core.deps.guava.base.Optional;
7+
import android.support.test.espresso.PerformException;
8+
import android.support.test.espresso.UiController;
9+
import android.support.test.espresso.ViewAction;
10+
import android.support.test.espresso.action.CoordinatesProvider;
11+
import android.support.test.espresso.action.PrecisionDescriber;
12+
import android.support.test.espresso.action.Tap;
13+
import android.support.test.espresso.action.Tapper;
14+
import android.support.test.espresso.util.HumanReadables;
15+
import android.view.View;
16+
import android.view.ViewConfiguration;
17+
import android.webkit.WebView;
18+
import org.hamcrest.Matcher;
19+
20+
/**
21+
* Enables clicking on views with 65% or greater displaying.
22+
*/
23+
public final class BetterClickAction implements ViewAction {
24+
private final CoordinatesProvider coordinatesProvider;
25+
private final Tapper tapper;
26+
private final PrecisionDescriber precisionDescriber;
27+
private final Optional<ViewAction> rollbackAction;
28+
public BetterClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
29+
PrecisionDescriber precisionDescriber) {
30+
this(tapper, coordinatesProvider, precisionDescriber, null);
31+
}
32+
public BetterClickAction(Tapper tapper, CoordinatesProvider coordinatesProvider,
33+
PrecisionDescriber precisionDescriber, ViewAction rollbackAction) {
34+
this.coordinatesProvider = coordinatesProvider;
35+
this.tapper = tapper;
36+
this.precisionDescriber = precisionDescriber;
37+
this.rollbackAction = Optional.fromNullable(rollbackAction);
38+
}
39+
@Override
40+
@SuppressWarnings("unchecked")
41+
public Matcher<View> getConstraints() {
42+
// Check that at least 65% of the element is displayed (instead of default 90%)
43+
Matcher<View> standardConstraint = isDisplayingAtLeast(65);
44+
if (rollbackAction.isPresent()) {
45+
return allOf(standardConstraint, rollbackAction.get().getConstraints());
46+
} else {
47+
return standardConstraint;
48+
}
49+
}
50+
@Override
51+
public void perform(UiController uiController, View view) {
52+
float[] coordinates = coordinatesProvider.calculateCoordinates(view);
53+
float[] precision = precisionDescriber.describePrecision();
54+
Tapper.Status status = Tapper.Status.FAILURE;
55+
int loopCount = 0;
56+
// Native event injection is quite a tricky process. A tap is actually 2
57+
// seperate motion events which need to get injected into the system. Injection
58+
// makes an RPC call from our app under test to the Android system server, the
59+
// system server decides which window layer to deliver the event to, the system
60+
// server makes an RPC to that window layer, that window layer delivers the event
61+
// to the correct UI element, activity, or window object. Now we need to repeat
62+
// that 2x. for a simple down and up. Oh and the down event triggers timers to
63+
// detect whether or not the event is a long vs. short press. The timers are
64+
// removed the moment the up event is received (NOTE: the possibility of eventTime
65+
// being in the future is totally ignored by most motion event processors).
66+
//
67+
// Phew.
68+
//
69+
// The net result of this is sometimes we'll want to do a regular tap, and for
70+
// whatever reason the up event (last half) of the tap is delivered after long
71+
// press timeout (depending on system load) and the long press behaviour is
72+
// displayed (EG: show a context menu). There is no way to avoid or handle this more
73+
// gracefully. Also the longpress behavour is app/widget specific. So if you have
74+
// a seperate long press behaviour from your short press, you can pass in a
75+
// 'RollBack' ViewAction which when executed will undo the effects of long press.
76+
while (status != Tapper.Status.SUCCESS && loopCount < 3) {
77+
try {
78+
status = tapper.sendTap(uiController, coordinates, precision);
79+
} catch (RuntimeException re) {
80+
throw new PerformException.Builder()
81+
.withActionDescription(this.getDescription())
82+
.withViewDescription(HumanReadables.describe(view))
83+
.withCause(re)
84+
.build();
85+
}
86+
int duration = ViewConfiguration.getPressedStateDuration();
87+
// ensures that all work enqueued to process the tap has been run.
88+
if (duration > 0) {
89+
uiController.loopMainThreadForAtLeast(duration);
90+
}
91+
if (status == Tapper.Status.WARNING) {
92+
if (rollbackAction.isPresent()) {
93+
rollbackAction.get().perform(uiController, view);
94+
} else {
95+
break;
96+
}
97+
}
98+
loopCount++;
99+
}
100+
if (status == Tapper.Status.FAILURE) {
101+
throw new PerformException.Builder()
102+
.withActionDescription(this.getDescription())
103+
.withViewDescription(HumanReadables.describe(view))
104+
.withCause(new RuntimeException(String.format("Couldn't "
105+
+ "click at: %s,%s precision: %s, %s . Tapper: %s coordinate provider: %s precision " +
106+
"describer: %s. Tried %s times. With Rollback? %s", coordinates[0], coordinates[1],
107+
precision[0], precision[1], tapper, coordinatesProvider, precisionDescriber, loopCount,
108+
rollbackAction.isPresent())))
109+
.build();
110+
}
111+
if (tapper == Tap.SINGLE && view instanceof WebView) {
112+
// WebViews will not process click events until double tap
113+
// timeout. Not the best place for this - but good for now.
114+
uiController.loopMainThreadForAtLeast(ViewConfiguration.getDoubleTapTimeout());
115+
}
116+
}
117+
@Override
118+
public String getDescription() {
119+
return tapper.toString().toLowerCase() + " click";
120+
}
121+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.wordpress.aztec.demo;
2+
3+
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
4+
import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
5+
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
6+
import static android.support.test.espresso.matcher.ViewMatchers.withEffectiveVisibility;
7+
import static org.hamcrest.Matchers.allOf;
8+
import static org.hamcrest.Matchers.anyOf;
9+
10+
import android.support.test.espresso.PerformException;
11+
import android.support.test.espresso.UiController;
12+
import android.support.test.espresso.ViewAction;
13+
import android.support.test.espresso.matcher.ViewMatchers.Visibility;
14+
import android.support.test.espresso.util.HumanReadables;
15+
16+
import android.graphics.Rect;
17+
import android.util.Log;
18+
import android.view.View;
19+
import android.widget.HorizontalScrollView;
20+
import android.widget.ScrollView;
21+
22+
import org.hamcrest.Matcher;
23+
24+
/**
25+
* Enables scrolling to the given view with 65% or greater displaying. View must be a descendant of a ScrollView.
26+
*/
27+
public final class BetterScrollToAction implements ViewAction {
28+
private static final String TAG = BetterScrollToAction.class.getSimpleName();
29+
30+
@SuppressWarnings("unchecked")
31+
@Override
32+
public Matcher<View> getConstraints() {
33+
return allOf(withEffectiveVisibility(Visibility.VISIBLE), isDescendantOfA(anyOf(
34+
isAssignableFrom(ScrollView.class), isAssignableFrom(HorizontalScrollView.class))));
35+
}
36+
37+
@Override
38+
public void perform(UiController uiController, View view) {
39+
// Check that at least 65% of the element is displayed (instead of default 90%)
40+
if (isDisplayingAtLeast(65).matches(view)) {
41+
Log.i(TAG, "View is already displayed. Returning.");
42+
return;
43+
}
44+
Rect rect = new Rect();
45+
view.getDrawingRect(rect);
46+
if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
47+
Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled.");
48+
}
49+
uiController.loopMainThreadUntilIdle();
50+
// Check that at least 65% of the element is displayed (instead of default 90%)
51+
if (!isDisplayingAtLeast(65).matches(view)) {
52+
throw new PerformException.Builder()
53+
.withActionDescription(this.getDescription())
54+
.withViewDescription(HumanReadables.describe(view))
55+
.withCause(new RuntimeException(
56+
"Scrolling to view was attempted, but the view is not displayed"))
57+
.build();
58+
}
59+
}
60+
61+
@Override
62+
public String getDescription() {
63+
return "scroll to";
64+
}
65+
}

0 commit comments

Comments
 (0)