aboutsummaryrefslogtreecommitdiff
path: root/src/io/appium/droiddriver/scroll/StepBasedScroller.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/io/appium/droiddriver/scroll/StepBasedScroller.java')
-rw-r--r--src/io/appium/droiddriver/scroll/StepBasedScroller.java168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/io/appium/droiddriver/scroll/StepBasedScroller.java b/src/io/appium/droiddriver/scroll/StepBasedScroller.java
new file mode 100644
index 0000000..6dbc79e
--- /dev/null
+++ b/src/io/appium/droiddriver/scroll/StepBasedScroller.java
@@ -0,0 +1,168 @@
+/*
+ * 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 io.appium.droiddriver.scroll;
+
+import android.util.Log;
+
+import io.appium.droiddriver.DroidDriver;
+import io.appium.droiddriver.Poller;
+import io.appium.droiddriver.UiElement;
+import io.appium.droiddriver.exceptions.ElementNotFoundException;
+import io.appium.droiddriver.exceptions.TimeoutException;
+import io.appium.droiddriver.finders.By;
+import io.appium.droiddriver.finders.Finder;
+import io.appium.droiddriver.scroll.Direction.Axis;
+import io.appium.droiddriver.scroll.Direction.DirectionConverter;
+import io.appium.droiddriver.scroll.Direction.PhysicalDirection;
+import io.appium.droiddriver.util.Logs;
+
+import static io.appium.droiddriver.scroll.Direction.LogicalDirection.BACKWARD;
+
+/**
+ * A {@link Scroller} that looks for the desired item in the currently shown
+ * content of the scrollable container, otherwise scrolls the container one step
+ * at a time and looks again, until we cannot scroll any more. A
+ * {@link ScrollStepStrategy} is used to determine whether more scrolling is
+ * possible.
+ */
+public class StepBasedScroller implements Scroller {
+ private final int maxScrolls;
+ private final long perScrollTimeoutMillis;
+ private final Axis axis;
+ private final ScrollStepStrategy scrollStepStrategy;
+ private final boolean startFromBeginning;
+
+ /**
+ * @param maxScrolls the maximum number of scrolls. It should be large enough
+ * to allow any reasonable list size
+ * @param perScrollTimeoutMillis the timeout in millis that we poll for the
+ * item after each scroll. 1000L is usually safe; if there are no
+ * asynchronously updated views, 0L is also a reasonable value.
+ * @param axis the axis this scroller can scroll
+ * @param startFromBeginning if {@code true},
+ * {@link #scrollTo(DroidDriver, Finder, Finder)} starts from the
+ * beginning and scrolls forward, instead of starting from the current
+ * location and scrolling in both directions. It may not always work,
+ * but when it works, it is faster.
+ */
+ public StepBasedScroller(int maxScrolls, long perScrollTimeoutMillis, Axis axis,
+ ScrollStepStrategy scrollStepStrategy, boolean startFromBeginning) {
+ this.maxScrolls = maxScrolls;
+ this.perScrollTimeoutMillis = perScrollTimeoutMillis;
+ this.axis = axis;
+ this.scrollStepStrategy = scrollStepStrategy;
+ this.startFromBeginning = startFromBeginning;
+ }
+
+ /**
+ * Constructs with default 100 maxScrolls, 1 second for
+ * perScrollTimeoutMillis, vertical axis, not startFromBegining.
+ */
+ public StepBasedScroller(ScrollStepStrategy scrollStepStrategy) {
+ this(100, 1000L, Axis.VERTICAL, scrollStepStrategy, false);
+ }
+
+ // if scrollBack is true, scrolls back to starting location if not found, so
+ // that we can start search in the other direction w/o polling on pages we
+ // have tried.
+ protected UiElement scrollTo(DroidDriver driver, Finder containerFinder, Finder itemFinder,
+ PhysicalDirection direction, boolean scrollBack) {
+ Logs.call(this, "scrollTo", driver, containerFinder, itemFinder, direction, scrollBack);
+ // Enforce itemFinder is relative to containerFinder.
+ // Combine with containerFinder to make itemFinder absolute.
+ itemFinder = By.chain(containerFinder, itemFinder);
+
+ int i = 0;
+ for (; i <= maxScrolls; i++) {
+ try {
+ return driver.getPoller()
+ .pollFor(driver, itemFinder, Poller.EXISTS, perScrollTimeoutMillis);
+ } catch (TimeoutException e) {
+ if (i < maxScrolls && !scrollStepStrategy.scroll(driver, containerFinder, direction)) {
+ break;
+ }
+ }
+ }
+
+ ElementNotFoundException exception = new ElementNotFoundException(itemFinder);
+ if (i == maxScrolls) {
+ // This is often a program error -- maxScrolls is a safety net; we should
+ // have either found itemFinder, or stopped scrolling b/c of reaching the
+ // end. If maxScrolls is reasonably large, ScrollStepStrategy must be
+ // wrong.
+ Logs.logfmt(Log.WARN, exception, "Scrolled %s %d times; ScrollStepStrategy=%s",
+ containerFinder, maxScrolls, scrollStepStrategy);
+ }
+
+ if (scrollBack) {
+ for (; i > 1; i--) {
+ driver.on(containerFinder).scroll(direction.reverse());
+ }
+ }
+ throw exception;
+ }
+
+ @Override
+ public UiElement scrollTo(DroidDriver driver, Finder containerFinder, Finder itemFinder,
+ PhysicalDirection direction) {
+ try {
+ scrollStepStrategy.beginScrolling(driver, containerFinder, itemFinder, direction);
+ return scrollTo(driver, containerFinder, itemFinder, direction, false);
+ } finally {
+ scrollStepStrategy.endScrolling(driver, containerFinder, itemFinder, direction);
+ }
+ }
+
+ @Override
+ public UiElement scrollTo(DroidDriver driver, Finder containerFinder, Finder itemFinder) {
+ Logs.call(this, "scrollTo", driver, containerFinder, itemFinder);
+ DirectionConverter converter = scrollStepStrategy.getDirectionConverter();
+ PhysicalDirection backwardDirection = converter.toPhysicalDirection(axis, BACKWARD);
+
+ if (startFromBeginning) {
+ // First try w/o scrolling
+ try {
+ return driver.getPoller().pollFor(driver, By.chain(containerFinder, itemFinder),
+ Poller.EXISTS, perScrollTimeoutMillis);
+ } catch (TimeoutException unused) {
+ // fall through to scroll to find
+ }
+
+ // Fling to beginning is not reliable; scroll to beginning
+ // container.perform(SwipeAction.toFling(backwardDirection));
+ try {
+ scrollStepStrategy.beginScrolling(driver, containerFinder, itemFinder, backwardDirection);
+ for (int i = 0; i < maxScrolls; i++) {
+ if (!scrollStepStrategy.scroll(driver, containerFinder, backwardDirection)) {
+ break;
+ }
+ }
+ } finally {
+ scrollStepStrategy.endScrolling(driver, containerFinder, itemFinder, backwardDirection);
+ }
+ } else {
+ // search backward first
+ try {
+ return scrollTo(driver, containerFinder, itemFinder, backwardDirection, true);
+ } catch (ElementNotFoundException e) {
+ // fall through to search forward
+ }
+ }
+
+ // search forward
+ return scrollTo(driver, containerFinder, itemFinder, backwardDirection.reverse(), false);
+ }
+}