aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKevin Jin <kjin@google.com>2014-04-22 15:58:00 -0700
committerKevin Jin <kjin@google.com>2014-04-23 08:48:59 -0700
commite0ad7adf66e62e536a0ce66fcb099a3518cda010 (patch)
treea62c4b0788675a528b99b0574895608a342b452d /src
parent17342a5115d7575d44a99fed9c7032e3ab316dcc (diff)
downloaddroiddriver-e0ad7adf66e62e536a0ce66fcb099a3518cda010.tar.gz
consolidate ByAttribute, MatchStrategy by Predicate
also simplified SentinelStrategy using Predicate. fix TestRunner when no activities are on back stack. remove deprecated UnrecoverableFailure since callers on old branches have been fixed. Change-Id: Ib9f59a6a0cd733eecec2e414588286f173628516
Diffstat (limited to 'src')
-rw-r--r--src/com/google/android/droiddriver/finders/By.java165
-rw-r--r--src/com/google/android/droiddriver/finders/ByAttribute.java39
-rw-r--r--src/com/google/android/droiddriver/finders/Finder.java2
-rw-r--r--src/com/google/android/droiddriver/finders/MatchFinder.java2
-rw-r--r--src/com/google/android/droiddriver/finders/MatchStrategy.java37
-rw-r--r--src/com/google/android/droiddriver/finders/Predicates.java142
-rw-r--r--src/com/google/android/droiddriver/helpers/BaseDroidDriverTest.java2
-rw-r--r--src/com/google/android/droiddriver/helpers/UnrecoverableFailure.java39
-rw-r--r--src/com/google/android/droiddriver/runner/TestRunner.java16
-rw-r--r--src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java109
-rw-r--r--src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java6
-rw-r--r--src/com/google/android/droiddriver/scroll/SentinelStrategy.java172
-rw-r--r--src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java2
13 files changed, 330 insertions, 403 deletions
diff --git a/src/com/google/android/droiddriver/finders/By.java b/src/com/google/android/droiddriver/finders/By.java
index e23a244..642b03d 100644
--- a/src/com/google/android/droiddriver/finders/By.java
+++ b/src/com/google/android/droiddriver/finders/By.java
@@ -32,150 +32,65 @@ public class By {
return ANY;
}
- /** Matches by {@link Object#equals}. */
- public static final MatchStrategy<Object> OBJECT_EQUALS = new MatchStrategy<Object>() {
- @Override
- public boolean match(Object expected, Object actual) {
- return actual == expected || (actual != null && actual.equals(expected));
- }
-
- @Override
- public String toString() {
- return "equals";
- }
- };
- /** Matches by {@link String#matches}. */
- public static final MatchStrategy<String> STRING_MATCHES = new MatchStrategy<String>() {
- @Override
- public boolean match(String expected, String actual) {
- return actual != null && actual.matches(expected);
- }
-
- @Override
- public String toString() {
- return "matches pattern";
- }
- };
- /** Matches by {@link String#contains}. */
- public static final MatchStrategy<String> STRING_CONTAINS = new MatchStrategy<String>() {
- @Override
- public boolean match(String expected, String actual) {
- return actual != null && actual.contains(expected);
- }
-
- @Override
- public String toString() {
- return "contains";
- }
- };
+ /** Matches a UiElement whose {@code attribute} is {@code true}. */
+ public static MatchFinder is(Attribute attribute) {
+ return new MatchFinder(Predicates.attributeTrue(attribute));
+ }
/**
- * Creates a new ByAttribute finder. Frequently-used finders have shorthands
- * below, for example, {@link #text}, {@link #textRegex}. Users can create
- * custom finders using this method.
- *
- * @param attribute the attribute to match against
- * @param strategy the matching strategy, for instance, equals or matches
- * regular expression
- * @param expected the expected attribute value
- * @return a new ByAttribute finder
+ * Matches a UiElement whose {@code attribute} is {@code false} or is not set.
*/
- public static <T> ByAttribute<T> attribute(Attribute attribute,
- MatchStrategy<? super T> strategy, T expected) {
- return new ByAttribute<T>(attribute, strategy, expected);
+ public static MatchFinder not(Attribute attribute) {
+ return new MatchFinder(Predicates.attributeFalse(attribute));
}
- /** Shorthand for {@link #attribute}{@code (attribute, OBJECT_EQUALS, expected)} */
- public static <T> ByAttribute<T> attribute(Attribute attribute, T expected) {
- return attribute(attribute, OBJECT_EQUALS, expected);
+ /** Matches a UiElement by resource id. */
+ public static MatchFinder resourceId(String resourceId) {
+ return new MatchFinder(Predicates.attributeEquals(Attribute.RESOURCE_ID, resourceId));
}
- /** Shorthand for {@link #attribute}{@code (attribute, true)} */
- public static ByAttribute<Boolean> is(Attribute attribute) {
- return attribute(attribute, true);
+ /** Matches a UiElement by package name. */
+ public static MatchFinder packageName(String name) {
+ return new MatchFinder(Predicates.attributeEquals(Attribute.PACKAGE, name));
}
- /** Shorthand for {@link #attribute}{@code (attribute, false)} */
- public static ByAttribute<Boolean> not(Attribute attribute) {
- return attribute(attribute, false);
+ /** Matches a UiElement by the exact text. */
+ public static MatchFinder text(String text) {
+ return new MatchFinder(Predicates.attributeEquals(Attribute.TEXT, text));
}
- /**
- * @param resourceId The resource id to match against
- * @return a finder to find an element by resource id
- */
- public static ByAttribute<String> resourceId(String resourceId) {
- return attribute(Attribute.RESOURCE_ID, OBJECT_EQUALS, resourceId);
+ /** Matches a UiElement whose text matches {@code regex}. */
+ public static MatchFinder textRegex(String regex) {
+ return new MatchFinder(Predicates.attributeMatches(Attribute.TEXT, regex));
}
- /**
- * @param name The exact package name to match against
- * @return a finder to find an element by package name
- */
- public static ByAttribute<String> packageName(String name) {
- return attribute(Attribute.PACKAGE, OBJECT_EQUALS, name);
+ /** Matches a UiElement whose text contains {@code substring}. */
+ public static MatchFinder textContains(String substring) {
+ return new MatchFinder(Predicates.attributeContains(Attribute.TEXT, substring));
}
- /**
- * @param text The exact text to match against
- * @return a finder to find an element by text
- */
- public static ByAttribute<String> text(String text) {
- return attribute(Attribute.TEXT, OBJECT_EQUALS, text);
+ /** Matches a UiElement by content description. */
+ public static MatchFinder contentDescription(String contentDescription) {
+ return new MatchFinder(Predicates.attributeEquals(Attribute.CONTENT_DESC, contentDescription));
}
- /**
- * @param regex The regular expression pattern to match against
- * @return a finder to find an element by text pattern
- */
- public static ByAttribute<String> textRegex(String regex) {
- return attribute(Attribute.TEXT, STRING_MATCHES, regex);
+ /** Matches a UiElement whose content description contains {@code substring}. */
+ public static MatchFinder contentDescriptionContains(String substring) {
+ return new MatchFinder(Predicates.attributeContains(Attribute.CONTENT_DESC, substring));
}
- /**
- * @param substring String inside a text field
- * @return a finder to find an element by text substring
- */
- public static ByAttribute<String> textContains(String substring) {
- return attribute(Attribute.TEXT, STRING_CONTAINS, substring);
- }
-
- /**
- * @param contentDescription The exact content description to match against
- * @return a finder to find an element by content description
- */
- public static ByAttribute<String> contentDescription(String contentDescription) {
- return attribute(Attribute.CONTENT_DESC, OBJECT_EQUALS, contentDescription);
+ /** Matches a UiElement by class name. */
+ public static MatchFinder className(String className) {
+ return new MatchFinder(Predicates.attributeEquals(Attribute.CLASS, className));
}
- /**
- * @param substring String inside a content description
- * @return a finder to find an element by content description substring
- */
- public static ByAttribute<String> contentDescriptionContains(String substring) {
- return attribute(Attribute.CONTENT_DESC, STRING_CONTAINS, substring);
- }
-
- /**
- * @param className The exact class name to match against
- * @return a finder to find an element by class name
- */
- public static ByAttribute<String> className(String className) {
- return attribute(Attribute.CLASS, OBJECT_EQUALS, className);
- }
-
- /**
- * @param clazz The class whose name is matched against
- * @return a finder to find an element by class name
- */
- public static ByAttribute<String> className(Class<?> clazz) {
+ /** Matches a UiElement by class name. */
+ public static MatchFinder className(Class<?> clazz) {
return className(clazz.getName());
}
- /**
- * @return a finder to find an element that is selected
- */
- public static ByAttribute<Boolean> selected() {
+ /** Matches a UiElement that is selected. */
+ public static MatchFinder selected() {
return is(Attribute.SELECTED);
}
@@ -304,8 +219,8 @@ public class By {
}
/**
- * Matches a UiElement whose descendant matches the given descendantFinder.
- * This could be VERY inefficient; consider {@link #xpath}.
+ * Matches a UiElement whose descendant (including self) matches the given
+ * descendantFinder. This could be VERY inefficient; consider {@link #xpath}.
*/
public static MatchFinder withDescendant(final MatchFinder descendantFinder) {
checkNotNull(descendantFinder);
@@ -327,5 +242,11 @@ public class By {
});
}
+ /** Matches a UiElement that does not match the provided {@code finder}. */
+ public static MatchFinder not(MatchFinder finder) {
+ checkNotNull(finder);
+ return new MatchFinder(Predicates.not(finder.predicate));
+ }
+
private By() {}
}
diff --git a/src/com/google/android/droiddriver/finders/ByAttribute.java b/src/com/google/android/droiddriver/finders/ByAttribute.java
deleted file mode 100644
index 8790334..0000000
--- a/src/com/google/android/droiddriver/finders/ByAttribute.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 DroidDriver committers
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.droiddriver.finders;
-
-import com.google.android.droiddriver.UiElement;
-
-/**
- * Matches UiElement by a single attribute.
- */
-public class ByAttribute<T> extends MatchFinder {
- protected ByAttribute(final Attribute attribute, final MatchStrategy<? super T> strategy,
- final T expected) {
- super(new Predicate<UiElement>() {
- @Override
- public boolean apply(UiElement element) {
- T value = element.get(attribute);
- return strategy.match(expected, value);
- }
-
- @Override
- public String toString() {
- return String.format("{%s %s %s}", attribute, strategy, expected);
- }
- });
- }
-}
diff --git a/src/com/google/android/droiddriver/finders/Finder.java b/src/com/google/android/droiddriver/finders/Finder.java
index 7c56d25..9a7dd1d 100644
--- a/src/com/google/android/droiddriver/finders/Finder.java
+++ b/src/com/google/android/droiddriver/finders/Finder.java
@@ -39,7 +39,7 @@ public interface Finder {
*
* <p>
* It is recommended that this method return the description of the finder,
- * for example, "{text equals OK}".
+ * for example, "{text=OK}".
*/
@Override
String toString();
diff --git a/src/com/google/android/droiddriver/finders/MatchFinder.java b/src/com/google/android/droiddriver/finders/MatchFinder.java
index 1ee60e0..df92f72 100644
--- a/src/com/google/android/droiddriver/finders/MatchFinder.java
+++ b/src/com/google/android/droiddriver/finders/MatchFinder.java
@@ -29,7 +29,7 @@ import com.google.android.droiddriver.util.Logs;
public class MatchFinder implements Finder {
protected final Predicate<? super UiElement> predicate;
- protected MatchFinder(Predicate<? super UiElement> predicate) {
+ public MatchFinder(Predicate<? super UiElement> predicate) {
if (predicate == null) {
this.predicate = Predicates.any();
} else {
diff --git a/src/com/google/android/droiddriver/finders/MatchStrategy.java b/src/com/google/android/droiddriver/finders/MatchStrategy.java
deleted file mode 100644
index 089f70b..0000000
--- a/src/com/google/android/droiddriver/finders/MatchStrategy.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2013 DroidDriver committers
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.droiddriver.finders;
-
-/**
- * Encapsulates the matching strategy.
- */
-public interface MatchStrategy<T> {
- /**
- * @return true if actual matches expected
- */
- boolean match(T expected, T actual);
-
- /**
- * {@inheritDoc}
- *
- * <p>
- * It is recommended that this method return the description of the matching
- * strategy, for example, "matches pattern".
- */
- @Override
- String toString();
-}
diff --git a/src/com/google/android/droiddriver/finders/Predicates.java b/src/com/google/android/droiddriver/finders/Predicates.java
index ecfafdd..36d2fa3 100644
--- a/src/com/google/android/droiddriver/finders/Predicates.java
+++ b/src/com/google/android/droiddriver/finders/Predicates.java
@@ -47,6 +47,51 @@ public final class Predicates {
}
/**
+ * Returns a predicate that is the negation of the provided {@code predicate}.
+ */
+ public static <T> Predicate<T> not(final Predicate<T> predicate) {
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T input) {
+ return !predicate.apply(input);
+ }
+
+ @Override
+ public String toString() {
+ return "not(" + predicate + ")";
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} if both arguments
+ * evaluate to {@code true}. The arguments are evaluated in order, and
+ * evaluation will be "short-circuited" as soon as a false predicate is found.
+ */
+ @SuppressWarnings("unchecked")
+ public static <T> Predicate<T> allOf(final Predicate<? super T> first,
+ final Predicate<? super T> second) {
+ if (first == null || first == ANY) {
+ return (Predicate<T>) second;
+ }
+ if (second == null || second == ANY) {
+ return (Predicate<T>) first;
+ }
+
+ return new Predicate<T>() {
+ @Override
+ public boolean apply(T input) {
+ return first.apply(input) && second.apply(input);
+ }
+
+ @Override
+ public String toString() {
+ return "allOf(" + first + ", " + second + ")";
+ }
+ };
+ }
+
+ /**
* Returns a predicate that evaluates to {@code true} if each of its
* components evaluates to {@code true}. The components are evaluated in
* order, and evaluation will be "short-circuited" as soon as a false
@@ -98,6 +143,103 @@ public final class Predicates {
};
}
+ /**
+ * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
+ * if its {@code attribute} is {@code true}.
+ */
+ public static Predicate<UiElement> attributeTrue(final Attribute attribute) {
+ return new Predicate<UiElement>() {
+ @Override
+ public boolean apply(UiElement element) {
+ Boolean actual = element.get(attribute);
+ return actual != null && actual;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s}", attribute);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
+ * if its {@code attribute} is {@code false}.
+ */
+ public static Predicate<UiElement> attributeFalse(final Attribute attribute) {
+ return new Predicate<UiElement>() {
+ @Override
+ public boolean apply(UiElement element) {
+ Boolean actual = element.get(attribute);
+ return actual == null || !actual;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{not %s}", attribute);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
+ * if its {@code attribute} equals {@code expected}.
+ */
+ public static Predicate<UiElement> attributeEquals(final Attribute attribute,
+ final Object expected) {
+ return new Predicate<UiElement>() {
+ @Override
+ public boolean apply(UiElement element) {
+ Object actual = element.get(attribute);
+ return actual == expected || (actual != null && actual.equals(expected));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s=%s}", attribute, expected);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
+ * if its {@code attribute} matches {@code regex}.
+ */
+ public static Predicate<UiElement> attributeMatches(final Attribute attribute, final String regex) {
+ return new Predicate<UiElement>() {
+ @Override
+ public boolean apply(UiElement element) {
+ String actual = element.get(attribute);
+ return actual != null && actual.matches(regex);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s matches %s}", attribute, regex);
+ }
+ };
+ }
+
+ /**
+ * Returns a predicate that evaluates to {@code true} on a {@link UiElement}
+ * if its {@code attribute} contains {@code substring}.
+ */
+ public static Predicate<UiElement> attributeContains(final Attribute attribute,
+ final String substring) {
+ return new Predicate<UiElement>() {
+ @Override
+ public boolean apply(UiElement element) {
+ String actual = element.get(attribute);
+ return actual != null && actual.contains(substring);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s contains %s}", attribute, substring);
+ }
+ };
+ }
+
public static Predicate<UiElement> withParent(final Predicate<? super UiElement> parentPredicate) {
return new Predicate<UiElement>() {
@Override
diff --git a/src/com/google/android/droiddriver/helpers/BaseDroidDriverTest.java b/src/com/google/android/droiddriver/helpers/BaseDroidDriverTest.java
index 3ce5030..d22cee9 100644
--- a/src/com/google/android/droiddriver/helpers/BaseDroidDriverTest.java
+++ b/src/com/google/android/droiddriver/helpers/BaseDroidDriverTest.java
@@ -44,7 +44,7 @@ public abstract class BaseDroidDriverTest<T extends Activity> extends
private static boolean classSetUpDone = false;
// In case of device-wide fatal errors, e.g. OOME, the remaining tests will
// fail and the messages will not help, so skip them.
- protected static boolean skipRemainingTests = false;
+ private static boolean skipRemainingTests = false;
// Prevent crash by uncaught exception.
private static volatile Throwable uncaughtException;
static {
diff --git a/src/com/google/android/droiddriver/helpers/UnrecoverableFailure.java b/src/com/google/android/droiddriver/helpers/UnrecoverableFailure.java
deleted file mode 100644
index 390af39..0000000
--- a/src/com/google/android/droiddriver/helpers/UnrecoverableFailure.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2013 DroidDriver committers
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.droiddriver.helpers;
-
-
-/**
- * When an {@code UnrecoverableFailure} occurs, the rest of the tests are going
- * to fail as well, therefore running them only adds noise to the report.
- * {@link BaseDroidDriverTest} will skip remaining tests when this is thrown.
- *
- * @deprecated Use
- * {@link com.google.android.droiddriver.exceptions.UnrecoverableException}
- * instead.
- */
-@Deprecated
-@SuppressWarnings("serial")
-public class UnrecoverableFailure extends RuntimeException {
- public UnrecoverableFailure(String message) {
- super(message);
- }
-
- public UnrecoverableFailure(Throwable throwable) {
- super(throwable);
- }
-}
diff --git a/src/com/google/android/droiddriver/runner/TestRunner.java b/src/com/google/android/droiddriver/runner/TestRunner.java
index 5047e6f..8e86a20 100644
--- a/src/com/google/android/droiddriver/runner/TestRunner.java
+++ b/src/com/google/android/droiddriver/runner/TestRunner.java
@@ -71,15 +71,17 @@ public class TestRunner extends InstrumentationTestRunner {
public void endTest(Test test) {
// Try to finish activity on best-effort basis - TestListener should
// not throw.
+ final Activity[] activitiesCopy;
+ synchronized (activities) {
+ if (activities.isEmpty()) {
+ return;
+ }
+ activitiesCopy = activities.toArray(new Activity[activities.size()]);
+ }
+
runOnMainSyncWithTimeLimit(new Runnable() {
@Override
public void run() {
- Activity[] activitiesCopy;;
- synchronized (activities) {
- activitiesCopy = new Activity[activities.size()];
- activitiesCopy = activities.toArray(activitiesCopy);
- }
-
for (Activity activity : activitiesCopy) {
if (!activity.isFinishing()) {
try {
@@ -202,7 +204,7 @@ public class TestRunner extends InstrumentationTestRunner {
Logs.log(Log.WARN, e, String.format(
"Timed out after %d milliseconds waiting for Instrumentation.runOnMainSync",
timeoutMillis));
- futureTask.cancel(true);
+ futureTask.cancel(false);
return false;
}
}
diff --git a/src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java
deleted file mode 100644
index c440bf2..0000000
--- a/src/com/google/android/droiddriver/scroll/BaseSentinelStrategy.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2013 DroidDriver committers
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.google.android.droiddriver.scroll;
-
-import android.util.Log;
-
-import com.google.android.droiddriver.DroidDriver;
-import com.google.android.droiddriver.UiElement;
-import com.google.android.droiddriver.exceptions.ElementNotFoundException;
-import com.google.android.droiddriver.finders.By;
-import com.google.android.droiddriver.finders.Finder;
-import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
-import com.google.android.droiddriver.scroll.Direction.LogicalDirection;
-import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
-import com.google.android.droiddriver.util.Logs;
-
-/**
- * Base class for {@link SentinelStrategy}.
- */
-public abstract class BaseSentinelStrategy implements SentinelStrategy {
-
- // Make sure sentinel exists in container
- private static class SentinelFinder implements Finder {
- private final Getter getter;
-
- public SentinelFinder(Getter getter) {
- this.getter = getter;
- }
-
- @Override
- public UiElement find(UiElement container) {
- UiElement sentinel = getter.getSentinel(container);
- if (sentinel == null) {
- throw new ElementNotFoundException(this);
- }
- Logs.log(Log.INFO, "Found match: " + sentinel);
- return sentinel;
- }
-
- @Override
- public String toString() {
- return String.format("SentinelFinder{%s}", getter);
- }
- }
-
- private final Getter backwardGetter;
- private final Getter forwardGetter;
- private final DirectionConverter directionConverter;
- private final SentinelFinder backwardSentinelFinder;
- private final SentinelFinder forwardSentinelFinder;
-
- protected BaseSentinelStrategy(Getter backwardGetter, Getter forwardGetter,
- DirectionConverter directionConverter) {
- this.backwardGetter = backwardGetter;
- this.forwardGetter = forwardGetter;
- this.directionConverter = directionConverter;
- this.backwardSentinelFinder = new SentinelFinder(backwardGetter);
- this.forwardSentinelFinder = new SentinelFinder(forwardGetter);
- }
-
- protected UiElement getSentinel(DroidDriver driver, Finder containerFinder,
- PhysicalDirection direction) {
- Logs.call(this, "getSentinel", driver, containerFinder, direction);
- Finder sentinelFinder;
- LogicalDirection logicalDirection = directionConverter.toLogicalDirection(direction);
- if (logicalDirection == LogicalDirection.BACKWARD) {
- sentinelFinder = By.chain(containerFinder, backwardSentinelFinder);
- } else {
- sentinelFinder = By.chain(containerFinder, forwardSentinelFinder);
- }
- return driver.on(sentinelFinder);
- }
-
- @Override
- public final DirectionConverter getDirectionConverter() {
- return directionConverter;
- }
-
- @Override
- public void beginScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder,
- PhysicalDirection direction) {}
-
- @Override
- public void endScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder,
- PhysicalDirection direction) {}
-
- @Override
- public String toString() {
- return String.format("{backwardGetter=%s, forwardGetter=%s}", backwardGetter, forwardGetter);
- }
-
- @Override
- public void doScroll(UiElement container, PhysicalDirection direction) {
- container.scroll(direction);
- }
-}
diff --git a/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
index 95778d0..b7ccce8 100644
--- a/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/DynamicSentinelStrategy.java
@@ -34,7 +34,7 @@ import com.google.android.droiddriver.util.Strings;
* used, which skips invisible children, or in the case of dynamic list, which
* shows more items when scrolling beyond the end.
*/
-public class DynamicSentinelStrategy extends BaseSentinelStrategy {
+public class DynamicSentinelStrategy extends SentinelStrategy {
/**
* Interface for determining whether sentinel is updated.
@@ -184,8 +184,8 @@ public class DynamicSentinelStrategy extends BaseSentinelStrategy {
*/
public DynamicSentinelStrategy(IsUpdatedStrategy isUpdatedStrategy, Getter backwardGetter,
Getter forwardGetter, DirectionConverter directionConverter) {
- super(new MorePredicateGetter(backwardGetter, UiElement.VISIBLE, "VISIBLE_"),
- new MorePredicateGetter(forwardGetter, UiElement.VISIBLE, "VISIBLE_"), directionConverter);
+ super(new MorePredicateGetter(backwardGetter, UiElement.VISIBLE), new MorePredicateGetter(
+ forwardGetter, UiElement.VISIBLE), directionConverter);
this.isUpdatedStrategy = isUpdatedStrategy;
}
diff --git a/src/com/google/android/droiddriver/scroll/SentinelStrategy.java b/src/com/google/android/droiddriver/scroll/SentinelStrategy.java
index f74e8a6..19c1258 100644
--- a/src/com/google/android/droiddriver/scroll/SentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/SentinelStrategy.java
@@ -15,83 +15,92 @@
*/
package com.google.android.droiddriver.scroll;
+import android.util.Log;
+
+import com.google.android.droiddriver.DroidDriver;
import com.google.android.droiddriver.UiElement;
+import com.google.android.droiddriver.exceptions.ElementNotFoundException;
+import com.google.android.droiddriver.finders.By;
+import com.google.android.droiddriver.finders.Finder;
import com.google.android.droiddriver.finders.Predicate;
import com.google.android.droiddriver.finders.Predicates;
+import com.google.android.droiddriver.scroll.Direction.DirectionConverter;
+import com.google.android.droiddriver.scroll.Direction.LogicalDirection;
+import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
+import com.google.android.droiddriver.util.Logs;
import java.util.List;
/**
- * Interface for determining whether scrolling is possible based on a sentinel.
+ * A {@link ScrollStepStrategy} that determines whether scrolling is possible
+ * based on a sentinel.
*/
-public interface SentinelStrategy extends ScrollStepStrategy {
-
+public abstract class SentinelStrategy implements ScrollStepStrategy {
/**
- * Gets sentinel based on {@link Predicate}.
+ * A {@link Finder} for sentinel. Note that unlike {@link Finder}, invisible
+ * UiElements are not skipped by default.
*/
- public static abstract class Getter {
+ public static abstract class Getter implements Finder {
protected final Predicate<? super UiElement> predicate;
- protected final String description;
- protected Getter(Predicate<? super UiElement> predicate, String description) {
+ protected Getter() {
+ // Include invisible children by default.
+ this(null);
+ }
+
+ protected Getter(Predicate<? super UiElement> predicate) {
this.predicate = predicate;
- this.description = description;
}
/**
* Gets the sentinel, which must be an immediate child of {@code container}
- * -- not a descendant. Note this could be null if {@code container} has not
- * finished updating.
+ * - not a descendant. Note sentinel may not exist if {@code container} has
+ * not finished updating.
*/
- public UiElement getSentinel(UiElement container) {
- return getSentinel(container.getChildren(predicate));
+ @Override
+ public UiElement find(UiElement container) {
+ UiElement sentinel = getSentinel(container.getChildren(predicate));
+ if (sentinel == null) {
+ throw new ElementNotFoundException(this);
+ }
+ Logs.log(Log.INFO, "Found sentinel: " + sentinel);
+ return sentinel;
}
+
protected abstract UiElement getSentinel(List<? extends UiElement> children);
@Override
- public String toString() {
- return description;
- }
+ public abstract String toString();
}
/**
- * Decorates an existing {@link Getter} by adding another {@link Predicate}.
+ * Returns the first child as the sentinel.
*/
- public static class MorePredicateGetter extends Getter {
- private final Getter original;
-
- public MorePredicateGetter(Getter original, Predicate<? super UiElement> extraPredicate,
- String extraDescription) {
- super(Predicates.allOf(original.predicate, extraPredicate), extraDescription
- + original.description);
- this.original = original;
- }
-
+ public static final Getter FIRST_CHILD_GETTER = new Getter() {
@Override
protected UiElement getSentinel(List<? extends UiElement> children) {
- return original.getSentinel(children);
+ return children.isEmpty() ? null : children.get(0);
}
- }
- /**
- * Returns the first child as the sentinel.
- */
- public static final Getter FIRST_CHILD_GETTER =
- new Getter(Predicates.any(), "FIRST_CHILD") {
- @Override
- protected UiElement getSentinel(List<? extends UiElement> children) {
- return children.isEmpty() ? null : children.get(0);
- }
- };
+ @Override
+ public String toString() {
+ return "FIRST_CHILD";
+ }
+ };
/**
* Returns the last child as the sentinel.
*/
- public static final Getter LAST_CHILD_GETTER = new Getter(Predicates.any(), "LAST_CHILD") {
+ public static final Getter LAST_CHILD_GETTER = new Getter() {
@Override
protected UiElement getSentinel(List<? extends UiElement> children) {
return children.isEmpty() ? null : children.get(children.size() - 1);
}
+
+ @Override
+ public String toString() {
+ return "LAST_CHILD";
+ }
};
/**
* Returns the second last child as the sentinel. Useful when the activity
@@ -103,22 +112,99 @@ public interface SentinelStrategy extends ScrollStepStrategy {
* uiautomatorviewer does, but could be a problem with InstrumentationDriver.
* </p>
*/
- public static final Getter SECOND_LAST_CHILD_GETTER = new Getter(Predicates.any(),
- "SECOND_LAST_CHILD") {
+ public static final Getter SECOND_LAST_CHILD_GETTER = new Getter() {
@Override
protected UiElement getSentinel(List<? extends UiElement> children) {
return children.size() < 2 ? null : children.get(children.size() - 2);
}
+
+ @Override
+ public String toString() {
+ return "SECOND_LAST_CHILD";
+ }
};
/**
* Returns the second child as the sentinel. Useful when the activity shows a
* fixed first child.
*/
- public static final Getter SECOND_CHILD_GETTER = new Getter(Predicates.any(),
- "SECOND_CHILD") {
+ public static final Getter SECOND_CHILD_GETTER = new Getter() {
@Override
protected UiElement getSentinel(List<? extends UiElement> children) {
return children.size() <= 1 ? null : children.get(1);
}
+
+ @Override
+ public String toString() {
+ return "SECOND_CHILD";
+ }
};
+
+ /**
+ * Decorates a {@link Getter} by adding another {@link Predicate}.
+ */
+ public static class MorePredicateGetter extends Getter {
+ private final Getter original;
+
+ public MorePredicateGetter(Getter original, Predicate<? super UiElement> extraPredicate) {
+ super(Predicates.allOf(original.predicate, extraPredicate));
+ this.original = original;
+ }
+
+ @Override
+ protected UiElement getSentinel(List<? extends UiElement> children) {
+ return original.getSentinel(children);
+ }
+
+ @Override
+ public String toString() {
+ return predicate.toString() + " " + original;
+ }
+ }
+
+ private final Getter backwardGetter;
+ private final Getter forwardGetter;
+ private final DirectionConverter directionConverter;
+
+ protected SentinelStrategy(Getter backwardGetter, Getter forwardGetter,
+ DirectionConverter directionConverter) {
+ this.backwardGetter = backwardGetter;
+ this.forwardGetter = forwardGetter;
+ this.directionConverter = directionConverter;
+ }
+
+ protected UiElement getSentinel(DroidDriver driver, Finder containerFinder,
+ PhysicalDirection direction) {
+ Logs.call(this, "getSentinel", driver, containerFinder, direction);
+ Finder sentinelFinder;
+ LogicalDirection logicalDirection = directionConverter.toLogicalDirection(direction);
+ if (logicalDirection == LogicalDirection.BACKWARD) {
+ sentinelFinder = By.chain(containerFinder, backwardGetter);
+ } else {
+ sentinelFinder = By.chain(containerFinder, forwardGetter);
+ }
+ return driver.on(sentinelFinder);
+ }
+
+ @Override
+ public final DirectionConverter getDirectionConverter() {
+ return directionConverter;
+ }
+
+ @Override
+ public void beginScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder,
+ PhysicalDirection direction) {}
+
+ @Override
+ public void endScrolling(DroidDriver driver, Finder containerFinder, Finder itemFinder,
+ PhysicalDirection direction) {}
+
+ @Override
+ public String toString() {
+ return String.format("{backwardGetter=%s, forwardGetter=%s}", backwardGetter, forwardGetter);
+ }
+
+ @Override
+ public void doScroll(UiElement container, PhysicalDirection direction) {
+ container.scroll(direction);
+ }
}
diff --git a/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java b/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java
index e8f1c76..4cd3bc7 100644
--- a/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java
+++ b/src/com/google/android/droiddriver/scroll/StaticSentinelStrategy.java
@@ -34,7 +34,7 @@ import com.google.android.droiddriver.scroll.Direction.PhysicalDirection;
* This does not work if a child is larger than the physical size of the
* container.
*/
-public class StaticSentinelStrategy extends BaseSentinelStrategy {
+public class StaticSentinelStrategy extends SentinelStrategy {
/**
* Defaults to FIRST_CHILD_GETTER for backward scrolling, LAST_CHILD_GETTER
* for forward scrolling, and the standard {@link DirectionConverter}.