Commit 8e225b97 authored by Andrew Luo's avatar Andrew Luo Committed by Commit Bot

Ui2Locators for Page Controllers.

Bug: 924194
Change-Id: I7cf3075dc2dda96206161b052ba9494643951839
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1492367
Commit-Queue: Andrew Luo <aluo@chromium.org>
Reviewed-by: default avatarJohn Budorick <jbudorick@chromium.org>
Reviewed-by: default avatarSky Malice <skym@chromium.org>
Reviewed-by: default avatarBrian Sheedy <bsheedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#657324}
parent 3a86b5c0
...@@ -4,6 +4,26 @@ ...@@ -4,6 +4,26 @@
import("//build/config/android/rules.gni") import("//build/config/android/rules.gni")
android_library("chrome_java_test_pagecontroller") {
testonly = true
java_files = [
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/BySelectorIndexUi2Locator.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/BySelectorUi2Locator.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/ChildIndexUi2Locator.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/IUi2Locator.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/PathUi2Locator.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/Ui2Locators.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/UiLocationException.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/utils/Utils.java",
]
deps = [
"//base:base_java",
"//base:base_java_test_support",
"//third_party/android_support_test_runner:runner_java",
"//third_party/ub-uiautomator:ub_uiautomator_java",
]
}
android_library("chrome_java_test_support") { android_library("chrome_java_test_support") {
testonly = true testonly = true
java_files = [ java_files = [
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.NonNull;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import java.util.List;
/**
* Locator wrapper around UiAutomator BySelector that supports indexing into found nodes.
*/
class BySelectorIndexUi2Locator implements IUi2Locator {
private final BySelectorUi2Locator mSelectorLocator;
private final int mIndex;
public BySelectorIndexUi2Locator(BySelector selector, int index) {
if (index < 0) {
throw new IllegalArgumentException("index must be >= 0");
}
mSelectorLocator = new BySelectorUi2Locator(selector);
mIndex = index;
}
@Override
public UiObject2 locateOne(@NonNull UiDevice device) {
return Utils.nullableGet(mSelectorLocator.locateAll(device), mIndex);
}
@Override
public UiObject2 locateOne(@NonNull UiObject2 root) {
return Utils.nullableGet(mSelectorLocator.locateAll(root), mIndex);
}
@Override
public List<UiObject2> locateAll(@NonNull UiDevice device) {
return Utils.nullableIntoList(locateOne(device));
}
@Override
public List<UiObject2> locateAll(@NonNull UiObject2 root) {
return Utils.nullableIntoList(locateOne(root));
}
@Override
public String toString() {
return "BySelectorIndexLocator{"
+ "mSelectorLocator=" + mSelectorLocator + ", mIndex=" + mIndex + '}';
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.NonNull;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import java.util.List;
/**
* Locator wrapper around UiAutomator BySelector.
*/
class BySelectorUi2Locator implements IUi2Locator {
private final BySelector mSelector;
public BySelectorUi2Locator(BySelector selector) {
mSelector = selector;
}
@Override
public UiObject2 locateOne(@NonNull UiDevice device) {
return device.findObject(mSelector);
}
@Override
public UiObject2 locateOne(@NonNull UiObject2 root) {
return root.findObject(mSelector);
}
@Override
public List<UiObject2> locateAll(@NonNull UiDevice device) {
return device.findObjects(mSelector);
}
@Override
public List<UiObject2> locateAll(@NonNull UiObject2 root) {
return root.findObjects(mSelector);
}
@Override
public String toString() {
return "BySelectorLocator{"
+ "mSelector=" + mSelector + '}';
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.NonNull;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import java.util.Arrays;
import java.util.List;
/**
* Locates a child node based on its position relative to its siblings.
*/
class ChildIndexUi2Locator implements IUi2Locator {
private final int mFirstChildIndex;
private final int[] mDescendantIndices;
/**
* Locates the nth child, recursively if more indices are specified.
*
* @param childIndex The index of the child.
* @param descendantIndices Optional additional indices of descendants.
*/
public ChildIndexUi2Locator(int childIndex, int... descendantIndices) {
mFirstChildIndex = childIndex;
mDescendantIndices = descendantIndices;
}
@Override
public UiObject2 locateOne(@NonNull UiDevice device) {
List<UiObject2> children = device.findObjects(By.depth(0));
UiObject2 child = Utils.nullableGet(children, mFirstChildIndex);
return child == null ? null : locateDescendant(child);
}
@Override
public UiObject2 locateOne(@NonNull UiObject2 root) {
List<UiObject2> children = root.getChildren();
UiObject2 child = Utils.nullableGet(children, mFirstChildIndex);
return child == null ? null : locateDescendant(child);
}
@Override
public List<UiObject2> locateAll(@NonNull UiDevice device) {
return Utils.nullableIntoList(locateOne(device));
}
@Override
public List<UiObject2> locateAll(@NonNull UiObject2 root) {
return Utils.nullableIntoList(locateOne(root));
}
@Override
public String toString() {
return "ChildIndex{"
+ "mFirstChildIndex=" + mFirstChildIndex
+ ", mDescendantIndices=" + Arrays.toString(mDescendantIndices) + '}';
}
// Go through list of descendants to find the last child.
private UiObject2 locateDescendant(@NonNull UiObject2 child) {
List<UiObject2> children;
for (int index : mDescendantIndices) {
children = child.getChildren();
if (children == null || children.size() <= index) return null;
child = children.get(index);
}
return child;
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.Nullable;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import java.util.List;
/**
* This interface unifies the various ways to find a UI node.
*/
public interface IUi2Locator {
/**
* Locates a single node among all nodes found by the locator.
*
* @param device The device to search under.
* @return The first node found by the locator, or null if none is found.
* @throws android.support.test.uiautomator.StaleObjectException
*/
@Nullable
UiObject2 locateOne(UiDevice device);
/**
* Locates a single node among all nodes found by the locator.
*
* @param root The node to search under.
* @return The first node found by the locator, or null if none is found.
* @throws android.support.test.uiautomator.StaleObjectException
*/
@Nullable
UiObject2 locateOne(UiObject2 root);
/**
* Locates all nodes found by the locator.
*
* @param device The device to search under.
* @return All nodes found, or an empty list of none are found.
* @throws android.support.test.uiautomator.StaleObjectException
*/
List<UiObject2> locateAll(UiDevice device);
/**
* Locates all nodes found by the locator.
*
* @param root The node to search under.
* @return All nodes found, or an empty list of none are found.
* @throws android.support.test.uiautomator.StaleObjectException
*/
List<UiObject2> locateAll(UiObject2 root);
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.NonNull;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Locate node(s) satisfying the chain of IUi2Locators, rooting each iteration of the search at the
* node(s) found by the previous locator in the chain.
*/
class PathUi2Locator implements IUi2Locator {
private final IUi2Locator mFirstLocator;
private final IUi2Locator[] mAdditionalLocators;
/**
* Locates the node(s) matching the chain of locators.
*
* @param locator First locator in the chain.
* @param additionalLocators Optional, additional locators in the chain.
*/
public PathUi2Locator(@NonNull IUi2Locator firstLocator, IUi2Locator... additionalLocators) {
mFirstLocator = firstLocator;
mAdditionalLocators = additionalLocators;
}
@Override
public UiObject2 locateOne(UiDevice device) {
List<UiObject2> candidates = mFirstLocator.locateAll(device);
return Utils.nullableGet(locateRestOfPath(candidates), 0);
}
@Override
public UiObject2 locateOne(UiObject2 root) {
List<UiObject2> candidates = mFirstLocator.locateAll(root);
return Utils.nullableGet(locateRestOfPath(candidates), 0);
}
@Override
public List<UiObject2> locateAll(UiDevice device) {
List<UiObject2> candidates = mFirstLocator.locateAll(device);
return locateRestOfPath(candidates);
}
@Override
public List<UiObject2> locateAll(UiObject2 root) {
List<UiObject2> candidates = mFirstLocator.locateAll(root);
return locateRestOfPath(candidates);
}
@Override
public String toString() {
return "Path{"
+ "mFirstLocator=" + mFirstLocator
+ ", mAdditionalLocators=" + Arrays.toString(mAdditionalLocators) + '}';
}
/**
* Iterate through mAdditionalLocators, feeding results from each round to the next iteration.
*
* @param initialCandidates Input used as a starting point of the iteration.
* @return List of UiObject2 located by the last locator in mAdditionalLocators.
*/
private List<UiObject2> locateRestOfPath(@NonNull List<UiObject2> initialCandidates) {
List<UiObject2> currentObjects = new ArrayList<>();
currentObjects.addAll(initialCandidates);
for (int i = 0; i < mAdditionalLocators.length; i++) {
List<UiObject2> nextObjects = new ArrayList<>();
for (UiObject2 currentObject : currentObjects) {
nextObjects.addAll(mAdditionalLocators[i].locateAll(currentObject));
}
currentObjects.clear();
currentObjects.addAll(nextObjects);
}
return currentObjects;
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.NonNull;
import android.support.test.uiautomator.By;
import java.util.regex.Pattern;
/**
* Locators that find UIObject2 nodes.
*/
public final class Ui2Locators {
/**
* Locates the node(s) by position in the siblings list, recursively if more indices are
* specified.
*
* The order of the nodes is determined by the implementation of UiObject2.findObjects,
* but not documented, at present it's pre-order.
*
* @param childIndex The index of the child.
* @param descendantIndices Optional additional indices of descendants.
* @return A locator that will locate a child or descendant node by traversing
* the list of child indices.
*/
public static IUi2Locator withChildIndex(int childIndex, int... descendantIndices) {
return new ChildIndexUi2Locator(childIndex, descendantIndices);
}
/**
* Locates the node(s) at a certain depth.
*
* @param depth The depth.
* @return A locator that will locate the child(ren) at the given depth.
*/
public static IUi2Locator withChildDepth(int depth) {
return new BySelectorUi2Locator(By.depth(depth));
}
/**
* Locates the node(s) having a resource id name that matches a regex (excluding the package:id/
* part).
*
* @param idRegex Regular expression to match ids against.
* @return A locator that will find node(s) whose ids match the given regular
* expression.
*/
public static IUi2Locator withResIdRegex(@NonNull String idRegex) {
return withResNameRegex("^.*:id/" + idRegex + "$");
}
/**
* Locates the node(s) having a resource id name among a list of names (excluding the
* package:id/ part).
*
* @param firstId The first id to match against.
* @param additionalIds Optional ids to match against.
* @return A locator that will find node(s) if they match any of the ids.
*/
public static IUi2Locator withResIds(String firstId, String... additionalIds) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("(");
stringBuilder.append(firstId);
for (int i = 0; i < additionalIds.length; i++) {
stringBuilder.append("|" + additionalIds[i]);
}
stringBuilder.append(")");
return withResIdRegex(stringBuilder.toString());
}
/**
* Locates the node(s) having an exact resource name (including the package:id/ part).
*
* @param resourceName The resource name to find.
* @return A locator that will find node(s) whose resource name matches.
*/
public static IUi2Locator withResName(@NonNull String resourceName) {
return new BySelectorUi2Locator(By.res(resourceName));
}
/**
* Locates the node(s) having a resource name that match the regex (including the package:id/
* part).
*
* @param resourceNameRegex The resource name to find.
* @return A locator that will find node(s) whose resource name matches.
*/
public static IUi2Locator withResNameRegex(@NonNull String resourceNameRegex) {
return new BySelectorUi2Locator(By.res(Pattern.compile(resourceNameRegex)));
}
/**
* Locates the nth node having a resource name matching the regex (including the package:id/
* part).
*
* The order of the nodes is determined by the implementation of UiObject2.findObjects,
* but not documented, at present it's pre-order.
*
* @param resourceNameRegex The resource name to find.
* @param index The value of n.
* @return A locator that will find the nth node whose resource name matches.
*/
public static IUi2Locator withResNameRegexByIndex(
@NonNull String resourceNameRegex, int index) {
return new BySelectorIndexUi2Locator(By.res(Pattern.compile(resourceNameRegex)), index);
}
/**
* Locates the node(s) having an exact content description.
*
* @param desc The content description to match against.
* @return A locator that will find the node(s) with the given content description.
*
* @see <a
* href="https://developer.android.com/reference/android/view/View.html#getContentDescription()">getContentDescription</a>
*/
public static IUi2Locator withContentDesc(@NonNull String desc) {
return new BySelectorUi2Locator(By.desc(desc));
}
/**
* Locates the node(s) having an exact text string.
*
* @param text The text to match.
* @return A locator that will find the node(s) having the given text.
*
* @see <a
* href="https://developer.android.com/reference/android/widget/TextView.html#getText()">getText</a>
*/
public static IUi2Locator withText(@NonNull String text) {
return new BySelectorUi2Locator(By.text(text));
}
/**
* Locates the node(s) having a text string that matches a regex.
*
* @param textRegex The regular expression to match the text against.
* @return A locator that will find the node(s) with text that matches the
* given regular expression.
*
* @see <a
* href="https://developer.android.com/reference/android/widget/TextView.html#getText()">getText</a>
*/
public static IUi2Locator withTextRegex(@NonNull String textRegex) {
return new BySelectorUi2Locator(By.text(Pattern.compile(textRegex)));
}
/**
* Locates the node(s) having a text string that contains a substring.
*
* @param subText Substring to search in the text field.
* @return A locator that will find node(s) with text that contains the given string.
*
* @see <a
* href="https://developer.android.com/reference/android/widget/TextView.html#getText()">getText</a>
*/
public static IUi2Locator withTextContaining(@NonNull String subText) {
return new BySelectorUi2Locator((By.textContains(subText)));
}
/**
* Locates the node(s) having a class name that matches a regex.
*
* @param regex Regular expression for the class name.
* @return A locator that will find node(s) with class name that matches
* the given regular expression.
*/
public static IUi2Locator withClassRegex(@NonNull String regex) {
return new BySelectorUi2Locator((By.clazz(Pattern.compile(regex))));
}
/**
* Locates the node(s) matching the chain of locators.
*
* @param locator First locator in the chain.
* @param additionalLocators Optional, additional locators in the chain.
* @return A locator that will find node(s) mathcing the chain of
* locators.
*/
public static IUi2Locator withPath(
@NonNull IUi2Locator locator, IUi2Locator... additionalLocators) {
return new PathUi2Locator(locator, additionalLocators);
}
/**
* Locates the node(s) having an exact package name.
*
* @param packageName Exact package name to match.
* @return A locator that will find node(s) having the packageName.
*/
public static IUi2Locator withPackageName(@NonNull String packageName) {
return new BySelectorUi2Locator(By.pkg(packageName));
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.test.uiautomator.UiObject2;
/**
* Exception class that represents an unexpected failure when trying to find
* a UI element.
*/
public class UiLocationException extends IllegalStateException {
private final UiObject2 mUiObject2;
private final IUi2Locator mIUi2Locator;
public UiObject2 getUiObject2() {
return mUiObject2;
}
public IUi2Locator getIUi2Locator() {
return mIUi2Locator;
}
private UiLocationException(
String message, Throwable cause, UiObject2 mUiObject2, IUi2Locator mIUi2Locator) {
super(message, cause);
this.mUiObject2 = mUiObject2;
this.mIUi2Locator = mIUi2Locator;
}
/**
* Creates a UiLocationException exception.
*
* @param msg The error message.
* @return UiLocationException with msg.
*/
public static UiLocationException newInstance(String msg) {
return newInstance(msg, null, null);
}
/**
* Creates a UiLocationException exception.
*
* @param msg The error message.
* @param locator The locator that failed to find any nodes.
* @param root The root that the locator searched under, or null if all the nodes were
* searched.
* @return UiLocationException with msg, locator, and root.
*/
public static UiLocationException newInstance(String msg, IUi2Locator locator, UiObject2 root) {
if (root == null) {
return new UiLocationException(
msg + " Locator: " + locator + " on device", null, root, locator);
} else {
return new UiLocationException(
msg + " Locator: " + locator + " in " + root.toString(), null, root, locator);
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.test.pagecontroller.utils;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.Collections;
import java.util.List;
/**
* Internal utility methods for pagecontroller.utils package.
*/
final class Utils {
// Return empty list if t is null, else return a singleton list containing t.
static <T> List<T> nullableIntoList(@Nullable T t) {
return t == null ? Collections.<T>emptyList() : Collections.singletonList(t);
}
// Returns the index-th item in the list or null if it's out of bounds.
static @Nullable<T> T nullableGet(@NonNull List<T> list, int index) {
return index >= list.size() ? null : list.get(index);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment