diff options
Diffstat (limited to 'src/io/appium/droiddriver/scroll/StepBasedScroller.java')
-rw-r--r-- | src/io/appium/droiddriver/scroll/StepBasedScroller.java | 168 |
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); + } +} |