Skip to content

Commit 3d117ee

Browse files
committed
Add custom actions to fix page break test
1 parent 728446c commit 3d117ee

File tree

4 files changed

+203
-2
lines changed

4 files changed

+203
-2
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
Matcher<View> standardConstraint = isDisplayingAtLeast(65);
43+
if (rollbackAction.isPresent()) {
44+
return allOf(standardConstraint, rollbackAction.get().getConstraints());
45+
} else {
46+
return standardConstraint;
47+
}
48+
}
49+
@Override
50+
public void perform(UiController uiController, View view) {
51+
float[] coordinates = coordinatesProvider.calculateCoordinates(view);
52+
float[] precision = precisionDescriber.describePrecision();
53+
Tapper.Status status = Tapper.Status.FAILURE;
54+
int loopCount = 0;
55+
// Native event injection is quite a tricky process. A tap is actually 2
56+
// seperate motion events which need to get injected into the system. Injection
57+
// makes an RPC call from our app under test to the Android system server, the
58+
// system server decides which window layer to deliver the event to, the system
59+
// server makes an RPC to that window layer, that window layer delivers the event
60+
// to the correct UI element, activity, or window object. Now we need to repeat
61+
// that 2x. for a simple down and up. Oh and the down event triggers timers to
62+
// detect whether or not the event is a long vs. short press. The timers are
63+
// removed the moment the up event is received (NOTE: the possibility of eventTime
64+
// being in the future is totally ignored by most motion event processors).
65+
//
66+
// Phew.
67+
//
68+
// The net result of this is sometimes we'll want to do a regular tap, and for
69+
// whatever reason the up event (last half) of the tap is delivered after long
70+
// press timeout (depending on system load) and the long press behaviour is
71+
// displayed (EG: show a context menu). There is no way to avoid or handle this more
72+
// gracefully. Also the longpress behavour is app/widget specific. So if you have
73+
// a seperate long press behaviour from your short press, you can pass in a
74+
// 'RollBack' ViewAction which when executed will undo the effects of long press.
75+
while (status != Tapper.Status.SUCCESS && loopCount < 3) {
76+
try {
77+
status = tapper.sendTap(uiController, coordinates, precision);
78+
} catch (RuntimeException re) {
79+
throw new PerformException.Builder()
80+
.withActionDescription(this.getDescription())
81+
.withViewDescription(HumanReadables.describe(view))
82+
.withCause(re)
83+
.build();
84+
}
85+
int duration = ViewConfiguration.getPressedStateDuration();
86+
// ensures that all work enqueued to process the tap has been run.
87+
if (duration > 0) {
88+
uiController.loopMainThreadForAtLeast(duration);
89+
}
90+
if (status == Tapper.Status.WARNING) {
91+
if (rollbackAction.isPresent()) {
92+
rollbackAction.get().perform(uiController, view);
93+
} else {
94+
break;
95+
}
96+
}
97+
loopCount++;
98+
}
99+
if (status == Tapper.Status.FAILURE) {
100+
throw new PerformException.Builder()
101+
.withActionDescription(this.getDescription())
102+
.withViewDescription(HumanReadables.describe(view))
103+
.withCause(new RuntimeException(String.format("Couldn't "
104+
+ "click at: %s,%s precision: %s, %s . Tapper: %s coordinate provider: %s precision " +
105+
"describer: %s. Tried %s times. With Rollback? %s", coordinates[0], coordinates[1],
106+
precision[0], precision[1], tapper, coordinatesProvider, precisionDescriber, loopCount,
107+
rollbackAction.isPresent())))
108+
.build();
109+
}
110+
if (tapper == Tap.SINGLE && view instanceof WebView) {
111+
// WebViews will not process click events until double tap
112+
// timeout. Not the best place for this - but good for now.
113+
uiController.loopMainThreadForAtLeast(ViewConfiguration.getDoubleTapTimeout());
114+
}
115+
}
116+
@Override
117+
public String getDescription() {
118+
return tapper.toString().toLowerCase() + " click";
119+
}
120+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
if (isDisplayingAtLeast(65).matches(view)) {
40+
Log.i(TAG, "View is already displayed. Returning.");
41+
return;
42+
}
43+
Rect rect = new Rect();
44+
view.getDrawingRect(rect);
45+
if (!view.requestRectangleOnScreen(rect, true /* immediate */)) {
46+
Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled.");
47+
}
48+
uiController.loopMainThreadUntilIdle();
49+
if (!isDisplayingAtLeast(65).matches(view)) {
50+
throw new PerformException.Builder()
51+
.withActionDescription(this.getDescription())
52+
.withViewDescription(HumanReadables.describe(view))
53+
.withCause(new RuntimeException(
54+
"Scrolling to view was attempted, but the view is not displayed"))
55+
.build();
56+
}
57+
}
58+
59+
@Override
60+
public String getDescription() {
61+
return "scroll to";
62+
}
63+
}

app/src/androidTest/java/org/wordpress/aztec/demo/SimpleTextFormattingTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import static android.support.test.espresso.assertion.ViewAssertions.matches;
1616
import static android.support.test.espresso.matcher.ViewMatchers.withText;
1717
import static org.wordpress.aztec.demo.TestUtils.aztecText;
18+
import static org.wordpress.aztec.demo.TestUtils.betterClick;
19+
import static org.wordpress.aztec.demo.TestUtils.betterScrollTo;
1820
import static org.wordpress.aztec.demo.TestUtils.boldButton;
1921
import static org.wordpress.aztec.demo.TestUtils.formattedText;
2022
import static org.wordpress.aztec.demo.TestUtils.headingButton;
@@ -163,12 +165,12 @@ public void testSimpleMoreTagFormatting() {
163165
public void testSimplePageBreakFormatting() {
164166
// Enter text in visual editor with page break in between
165167
aztecText.perform(typeText(unformattedText));
166-
pageButton.perform(scrollTo(), click());
168+
pageButton.perform(betterScrollTo(), betterClick());
167169
aztecText.perform(typeTextIntoFocusedView(unformattedText));
168170

169171
// Check that page break was correctly added
170172
toggleHTMLView();
171-
sourceText.check(matches(withText(unformattedText + "\n\n<!--pagebreak-->\n\n" + unformattedText)));
173+
sourceText.check(matches(withText(unformattedText + "\n\n<!--nextpage-->\n\n" + unformattedText)));
172174
}
173175

174176
@Test

app/src/androidTest/java/org/wordpress/aztec/demo/TestUtils.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package org.wordpress.aztec.demo;
22

33
import android.support.test.espresso.DataInteraction;
4+
import android.support.test.espresso.ViewAction;
45
import android.support.test.espresso.ViewInteraction;
6+
import android.support.test.espresso.action.GeneralLocation;
7+
import android.support.test.espresso.action.Press;
8+
import android.support.test.espresso.action.Tap;
9+
import android.support.test.espresso.action.ViewActions;
510

611
import static android.support.test.espresso.Espresso.onData;
712
import static android.support.test.espresso.Espresso.onView;
@@ -52,8 +57,19 @@ public class TestUtils {
5257
public static ViewInteraction linkTextField = onView(withId(R.id.linkText));
5358
public static ViewInteraction linkURLField = onView(withId(R.id.linkURL));
5459

60+
// Switch to HTML view
5561
public static void toggleHTMLView() {
5662
ViewInteraction htmlButton = onView(withId(R.id.format_bar_button_html));
5763
htmlButton.perform(click());
5864
}
65+
66+
// Better scrolling action for last toolbar item (<90% of item displayed)
67+
public static ViewAction betterScrollTo() {
68+
return ViewActions.actionWithAssertions(new BetterScrollToAction());
69+
}
70+
71+
// Better click action for last toolbar item (<90% of item displayed)
72+
public static ViewAction betterClick() {
73+
return new BetterClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER);
74+
}
5975
}

0 commit comments

Comments
 (0)