Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Bump androidx.test.uiautomator:uiautomator version to 2.3.0-beta01 #591

Merged
merged 6 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ apply plugin: 'com.android.application'
apply plugin: 'de.mobilej.unmock'

android {
compileSdk 33
compileSdk 34
defaultConfig {
applicationId 'io.appium.uiautomator2'
minSdkVersion 21
targetSdkVersion 33
targetSdkVersion 34
KazuCocoa marked this conversation as resolved.
Show resolved Hide resolved
versionCode 151
archivesBaseName = 'appium-uiautomator2'
/**
Expand Down Expand Up @@ -89,7 +89,7 @@ unMock {
dependencies {
// https://download.eclipse.org/oomph/archive/reports/download.eclipse.org/releases/2021-09/index/org.eclipse.wst.xml.xpath2.processor_2.1.101.v201903222120.html
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'androidx.test.uiautomator:uiautomator:2.2.0'
implementation 'androidx.test.uiautomator:uiautomator:2.3.0-beta01'
implementation 'androidx.test:core:1.5.0'
implementation 'androidx.test:runner:1.5.2'
implementation 'com.google.code.gson:gson:2.10.1'
Expand Down
207 changes: 118 additions & 89 deletions app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,22 @@
import android.os.Build;
import android.os.Bundle;
import android.util.Pair;
import android.view.Display;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityWindowInfo;

import androidx.annotation.Nullable;
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiDevice;

import java.util.HashSet;
import java.util.Set;

import io.appium.uiautomator2.common.exceptions.InvalidElementStateException;
import io.appium.uiautomator2.model.internal.CustomUiDevice;
import io.appium.uiautomator2.model.internal.GestureController;
import io.appium.uiautomator2.model.settings.Settings;
import io.appium.uiautomator2.model.settings.SimpleBoundsCalculation;
import io.appium.uiautomator2.model.settings.SnapshotMaxDepth;
import io.appium.uiautomator2.utils.Logger;

import static io.appium.uiautomator2.utils.Device.getUiDevice;
import static io.appium.uiautomator2.utils.ReflectionUtils.getField;
import static io.appium.uiautomator2.utils.StringHelpers.charSequenceToNullableString;
import static io.appium.uiautomator2.utils.StringHelpers.charSequenceToString;
Expand All @@ -53,6 +50,13 @@ public class AxNodeInfoHelper {
private static final long UNDEFINED_NODE_ID =
(((long) Integer.MAX_VALUE) << 32) | Integer.MAX_VALUE;
private static final int UNDEFINED_WINDOW_ID = -1;
private static final float DEFAULT_GESTURE_MARGIN_PERCENT = 0.1f;
private static final Margins mMargins = new PercentMargins(
DEFAULT_GESTURE_MARGIN_PERCENT,
DEFAULT_GESTURE_MARGIN_PERCENT,
DEFAULT_GESTURE_MARGIN_PERCENT,
DEFAULT_GESTURE_MARGIN_PERCENT
);

@Nullable
public static String toUuid(AccessibilityNodeInfo info) {
Expand Down Expand Up @@ -114,26 +118,17 @@ private static Point getCenterPoint(Rect bounds) {

private static Rect getBoundsForGestures(AccessibilityNodeInfo node) {
Rect bounds = getBounds(node);
// The default margin values are copied from UiObject2 class:
// private int mMarginLeft = 5;
// private int mMarginTop = 5;
// private int mMarginRight = 5;
// private int mMarginBottom = 5;
bounds.left = bounds.left + 5;
bounds.top = bounds.top + 5;
bounds.right = bounds.right - 5;
bounds.bottom = bounds.bottom - 5;
return bounds;
return mMargins.apply(bounds);
}

public static void click(AccessibilityNodeInfo node) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().click(getCenterPoint(bounds));
makeGestureController(node).click(getCenterPoint(bounds));
}

public static void doubleClick(AccessibilityNodeInfo node) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().doubleClick(getCenterPoint(bounds));
makeGestureController(node).doubleClick(getCenterPoint(bounds));
}

public static void longClick(AccessibilityNodeInfo node) {
Expand All @@ -142,7 +137,7 @@ public static void longClick(AccessibilityNodeInfo node) {

public static void longClick(AccessibilityNodeInfo node, @Nullable Long durationMs) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().longClick(getCenterPoint(bounds), durationMs);
makeGestureController(node).longClick(getCenterPoint(bounds), durationMs);
}

public static void drag(AccessibilityNodeInfo node, Point end) {
Expand All @@ -151,7 +146,7 @@ public static void drag(AccessibilityNodeInfo node, Point end) {

public static void drag(AccessibilityNodeInfo node, Point end, @Nullable Integer speed) {
Rect bounds = getBounds(node);
CustomUiDevice.getInstance().getGestureController().drag(getCenterPoint(bounds), end, speed);
makeGestureController(node).drag(getCenterPoint(bounds), end, speed);
}

public static void pinchClose(AccessibilityNodeInfo node, float percent) {
Expand All @@ -160,7 +155,21 @@ public static void pinchClose(AccessibilityNodeInfo node, float percent) {

public static void pinchClose(AccessibilityNodeInfo node, float percent, @Nullable Integer speed) {
Rect bounds = getBoundsForGestures(node);
CustomUiDevice.getInstance().getGestureController().pinchClose(bounds, percent, speed);
makeGestureController(node).pinchClose(bounds, percent, speed);
}

private static GestureController makeGestureController(AccessibilityNodeInfo node) {
return CustomUiDevice.getInstance().getGestureController(getAxNodeDisplayId(node));
}

public static int getAxNodeDisplayId(AccessibilityNodeInfo node) {
if (node != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AccessibilityWindowInfo window = node.getWindow();
if (window != null) {
return window.getDisplayId();
}
}
return Display.DEFAULT_DISPLAY;
}

public static void pinchOpen(AccessibilityNodeInfo node, float percent) {
Expand All @@ -169,54 +178,101 @@ public static void pinchOpen(AccessibilityNodeInfo node, float percent) {

public static void pinchOpen(AccessibilityNodeInfo node, float percent, @Nullable Integer speed) {
Rect bounds = getBoundsForGestures(node);
CustomUiDevice.getInstance().getGestureController().pinchOpen(bounds, percent, speed);
makeGestureController(node).pinchOpen(bounds, percent, speed);
}

public static void swipe(AccessibilityNodeInfo node, Direction direction, float percent) {
swipe(node, direction, percent, null);
}

public static void swipe(AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed) {
public static void swipe(
AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed
) {
Rect bounds = getBoundsForGestures(node);
CustomUiDevice.getInstance().getGestureController().swipe(bounds, direction, percent, speed);
makeGestureController(node).swipe(bounds, direction, percent, speed);
}

public static boolean scroll(AccessibilityNodeInfo node, Direction direction, float percent) {
return scroll(node, direction, percent, null);
}

public static boolean scroll(AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed) {
public static boolean scroll(
AccessibilityNodeInfo node, Direction direction, float percent, @Nullable Integer speed
) {
Rect bounds = getBoundsForGestures(node);
return CustomUiDevice.getInstance().getGestureController().scroll(bounds, direction, percent, speed);
return makeGestureController(node).scroll(bounds, direction, percent, speed);
}

public static boolean fling(AccessibilityNodeInfo node, Direction direction) {
return fling(node, direction, null);
}

public static boolean fling(AccessibilityNodeInfo node, Direction direction, @Nullable Integer speed) {
public static boolean fling(
AccessibilityNodeInfo node, Direction direction, @Nullable Integer speed
) {
Rect bounds = getBoundsForGestures(node);
return CustomUiDevice.getInstance().getGestureController().fling(bounds, direction, speed);
return makeGestureController(node).fling(bounds, direction, speed);
}

/**
* Returns the node's bounds clipped to the size of the display
*
* @return Empty Rect if node is null, else a Rect containing visible bounds
*/
public static Rect getBounds(@Nullable AccessibilityNodeInfo node) {
Rect rect = new Rect();
int displayId = AxNodeInfoHelper.getAxNodeDisplayId(node);
final boolean isDisplayAccessible = CustomUiDevice.getInstance().getDisplayById(displayId) != null;
Rect screen = null;
if (isDisplayAccessible) {
Point displaySize = CustomUiDevice.getInstance().getDisplaySize(displayId);
screen = new Rect(0, 0, displaySize.x, displaySize.y);
}
if (node == null) {
return rect;
return screen == null ? new Rect() : screen;
}
if (Settings.get(SimpleBoundsCalculation.class).getValue()) {
node.getBoundsInScreen(rect);
return rect;
return getVisibleBoundsInScreen(
node,
screen,
Boolean.FALSE.equals(Settings.get(SimpleBoundsCalculation.class).getValue()),
0
);
}

@SuppressLint("CheckResult")
private static Rect getVisibleBoundsInScreen(
AccessibilityNodeInfo node, Rect displayRect, boolean trimScrollableParent, int depth
) {
Rect nodeRect = new Rect();
node.getBoundsInScreen(nodeRect);

if (displayRect == null) {
displayRect = new Rect();
}
nodeRect.intersect(displayRect);

// Trim any portion of the bounds that are outside the window
Rect bounds = new Rect();
AccessibilityWindowInfo window = node.getWindow();
if (window != null) {
window.getBoundsInScreen(bounds);
nodeRect.intersect(bounds);
}

UiDevice uiDevice = getUiDevice();
Rect screenRect = new Rect(0, 0, uiDevice.getDisplayWidth(), uiDevice.getDisplayHeight());
return getBounds(node, screenRect, 0);
// Trim the bounds into any scrollable ancestor, if required.
if (trimScrollableParent) {
for (AccessibilityNodeInfo ancestor = node.getParent();
ancestor != null;
ancestor = ancestor.getParent()
) {
if (ancestor.isScrollable()) {
if (depth >= Settings.get(SnapshotMaxDepth.class).getValue()) {
break;
}
Rect ancestorRect = getVisibleBoundsInScreen(
ancestor, displayRect, true, depth + 1
);
nodeRect.intersect(ancestorRect);
break;
}
}
}

return nodeRect;
}

public static int calculateIndex(AccessibilityNodeInfo node) {
Expand All @@ -232,55 +288,6 @@ public static int calculateIndex(AccessibilityNodeInfo node) {
return 0;
}

/**
* Returns the node's bounds clipped to the size of the display, limited by the SnapshotMaxDepth
* The implementation is borrowed from `getVisibleBounds` method of `UiObject2` class
*
* @return Empty rect if node is null, else a Rect containing visible bounds
*/
@SuppressLint("CheckResult")
private static Rect getBounds(@Nullable AccessibilityNodeInfo node, Rect displayRect, int depth) {
Rect ret = new Rect();
if (node == null) {
return ret;
}

// Get the object bounds in screen coordinates
node.getBoundsInScreen(ret);

// Trim any portion of the bounds that are not on the screen
ret.intersect(displayRect);

// Trim any portion of the bounds that are outside the window
Rect window = new Rect();
AccessibilityWindowInfo nodeWindow = node.getWindow();
if (nodeWindow != null) {
nodeWindow.getBoundsInScreen(window);
ret.intersect(window);
}

// Find the visible bounds of our first scrollable ancestor
int currentDepth = depth;
Set<AccessibilityNodeInfo> ancestors = new HashSet<>();
AccessibilityNodeInfo ancestor = node.getParent();
// An erroneous situation is possible where node parent equals to the node itself
while (++currentDepth < Settings.get(SnapshotMaxDepth.class).getValue()
&& ancestor != null && !ancestors.contains(ancestor)) {
// If this ancestor is scrollable
if (ancestor.isScrollable()) {
// Trim any portion of the bounds that are hidden by the non-visible portion of our
// ancestor
Rect ancestorRect = getBounds(ancestor, displayRect, currentDepth);
ret.intersect(ancestorRect);
return ret;
}
ancestors.add(ancestor);
ancestor = ancestor.getParent();
}

return ret;
}

/**
* Perform accessibility action ACTION_SET_PROGRESS on the node
*
Expand Down Expand Up @@ -330,4 +337,26 @@ public static String truncateTextToMaxLength(final AccessibilityNodeInfo node, f
}
return text;
}

private interface Margins {
Rect apply(Rect bounds);
}

private static class PercentMargins implements Margins {
float mLeft, mTop, mRight, mBottom;
PercentMargins(float left, float top, float right, float bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}

@Override
public Rect apply(Rect bounds) {
return new Rect(bounds.left + (int) (bounds.width() * mLeft),
bounds.top + (int) (bounds.height() * mTop),
bounds.right - (int) (bounds.width() * mRight),
bounds.bottom - (int) (bounds.height() * mBottom));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ protected AppiumResponse safeHandle(IHttpRequest request) {
Rect bounds = element.getBounds();
Point start = new Point(bounds.left + dragModel.start.x.intValue(),
bounds.top + dragModel.start.y.intValue());
CustomUiDevice.getInstance().getGestureController().drag(start, dragModel.end.toNativePoint(),
dragModel.speed);
CustomUiDevice.getInstance().getGestureController(element).drag(
start, dragModel.end.toNativePoint(), dragModel.speed
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public interface AndroidElement {

@Nullable String getContextId();

int getDisplayId();

boolean isSingleMatch();

void clear() throws UiObjectNotFoundException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ public String getAttribute(String attr) throws UiObjectNotFoundException {
return (result instanceof String) ? (String) result : String.valueOf(result);
}

@Override
public int getDisplayId() {
return element.getDisplayId();
}

@Override
public void clear() {
element.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.appium.uiautomator2.utils.PositionHelper;

import static io.appium.uiautomator2.core.AxNodeInfoExtractor.toAxNodeInfo;
import static io.appium.uiautomator2.core.AxNodeInfoHelper.getAxNodeDisplayId;
import static io.appium.uiautomator2.model.AccessibleUiObject.toAccessibleUiObject;
import static io.appium.uiautomator2.model.AccessibleUiObject.toAccessibleUiObjects;
import static io.appium.uiautomator2.utils.ElementHelpers.generateNoAttributeException;
Expand Down Expand Up @@ -152,6 +153,11 @@ public String getAttribute(String attr) throws UiObjectNotFoundException {
return (result instanceof String) ? (String) result : String.valueOf(result);
}

@Override
public int getDisplayId() {
return getAxNodeDisplayId(toAxNodeInfo(element));
}

@Override
public void clear() throws UiObjectNotFoundException {
element.setText("");
Expand Down
Loading
Loading