From d9d4082a7fa70aa474c90a29bed7ec6b2b4a4fe2 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 2 Jan 2024 13:58:33 +0100 Subject: [PATCH 1/6] feat!: Bump androidx.test.uiautomator:uiautomator version to 2.3.0-beta01 --- app/build.gradle | 6 +- .../uiautomator2/core/AxNodeInfoHelper.java | 34 +++++--- .../uiautomator2/handler/gestures/Drag.java | 5 +- .../uiautomator2/model/AndroidElement.java | 2 + .../uiautomator2/model/UiObject2Element.java | 5 ++ .../uiautomator2/model/UiObjectElement.java | 6 ++ .../model/internal/CustomUiDevice.java | 79 ++++++++----------- .../model/internal/GestureController.java | 22 +++++- .../uiautomator2/model/internal/Gestures.java | 66 ++++++++++------ .../uiautomator2/utils/gestures/Click.java | 2 +- .../utils/gestures/DoubleClick.java | 2 +- .../utils/gestures/LongClick.java | 2 +- 12 files changed, 141 insertions(+), 90 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index fed25a391..51c18925b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 versionCode 151 archivesBaseName = 'appium-uiautomator2' /** @@ -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' diff --git a/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java b/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java index 66326d97d..1dd54cbad 100644 --- a/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java +++ b/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java @@ -23,6 +23,7 @@ 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; @@ -36,6 +37,7 @@ 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; @@ -128,12 +130,12 @@ private static Rect getBoundsForGestures(AccessibilityNodeInfo node) { 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) { @@ -142,7 +144,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) { @@ -151,7 +153,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) { @@ -160,7 +162,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 (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) { @@ -169,7 +185,7 @@ 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) { @@ -178,7 +194,7 @@ public static void swipe(AccessibilityNodeInfo node, Direction direction, float 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) { @@ -187,7 +203,7 @@ public static boolean scroll(AccessibilityNodeInfo node, Direction direction, fl 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) { @@ -196,7 +212,7 @@ public static boolean fling(AccessibilityNodeInfo node, Direction direction) { 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); } /** diff --git a/app/src/main/java/io/appium/uiautomator2/handler/gestures/Drag.java b/app/src/main/java/io/appium/uiautomator2/handler/gestures/Drag.java index 0d9892eeb..f047c9bcc 100644 --- a/app/src/main/java/io/appium/uiautomator2/handler/gestures/Drag.java +++ b/app/src/main/java/io/appium/uiautomator2/handler/gestures/Drag.java @@ -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 + ); } } diff --git a/app/src/main/java/io/appium/uiautomator2/model/AndroidElement.java b/app/src/main/java/io/appium/uiautomator2/model/AndroidElement.java index a3c7d53c7..6e654876b 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/AndroidElement.java +++ b/app/src/main/java/io/appium/uiautomator2/model/AndroidElement.java @@ -30,6 +30,8 @@ public interface AndroidElement { @Nullable String getContextId(); + int getDisplayId(); + boolean isSingleMatch(); void clear() throws UiObjectNotFoundException; diff --git a/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java b/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java index 004a6ee67..e7bc15716 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java +++ b/app/src/main/java/io/appium/uiautomator2/model/UiObject2Element.java @@ -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(); diff --git a/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java b/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java index 510bccda3..2da9e5504 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java +++ b/app/src/main/java/io/appium/uiautomator2/model/UiObjectElement.java @@ -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; @@ -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(""); diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java index a16ad99ed..d0f719c38 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java @@ -40,6 +40,7 @@ import io.appium.uiautomator2.common.exceptions.InvalidSelectorException; import io.appium.uiautomator2.common.exceptions.UiAutomator2Exception; import io.appium.uiautomator2.model.AccessibleUiObject; +import io.appium.uiautomator2.model.AndroidElement; import io.appium.uiautomator2.model.ScreenRotation; import io.appium.uiautomator2.utils.Device; import io.appium.uiautomator2.utils.Logger; @@ -65,15 +66,24 @@ public class CustomUiDevice { private final Class ByMatcherClass; private final Constructor uiObject2Constructor; private final Instrumentation mInstrumentation; - private GestureController gestureController; - + private final Object nativeGestureController; private CustomUiDevice() { this.mInstrumentation = (Instrumentation) getField(UiDevice.class, FIELD_M_INSTRUMENTATION, Device.getUiDevice()); this.ByMatcherClass = ReflectionUtils.getClass("androidx.test.uiautomator.ByMatcher"); - this.METHOD_FIND_MATCH = getMethod(ByMatcherClass, "findMatch", UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class); - this.METHOD_FIND_MATCHES = getMethod(ByMatcherClass, "findMatches", UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class); - this.uiObject2Constructor = getConstructor(UiObject2.class, UiDevice.class, BySelector.class, AccessibilityNodeInfo.class); + this.METHOD_FIND_MATCH = getMethod( + ByMatcherClass, "findMatch", + UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class + ); + this.METHOD_FIND_MATCHES = getMethod( + ByMatcherClass, "findMatches", + UiDevice.class, BySelector.class, AccessibilityNodeInfo[].class + ); + this.uiObject2Constructor = getConstructor( + UiObject2.class, + UiDevice.class, BySelector.class, AccessibilityNodeInfo.class + ); + this.nativeGestureController = getNativeGestureControllerInstance(); } public static synchronized CustomUiDevice getInstance() { @@ -96,17 +106,6 @@ public UiAutomation getUiAutomation() { } private UiObject2 toUiObject2(@NonNull BySelector selector, @Nullable AccessibilityNodeInfo node) { - // TODO: remove this comment after upgrading to androidx.test.uiautomator:uiautomator:2.3.0 - // UiObject2 with androidx.test.uiautomator:uiautomator:2.3.0 has below code to crate the instance, - // thus if the node was None, it should create an empty element for the AccessibilityNodeInfo. - //
-        //    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-        //        AccessibilityWindowInfo window = UiObject2.Api21Impl.getWindow(cachedNode);
-        //        mDisplayId = window == null ? Display.DEFAULT_DISPLAY : UiObject2.Api30Impl.getDisplayId(window);
-        //    } else {
-        //        mDisplayId = Display.DEFAULT_DISPLAY;
-        //    }
-        // 
AccessibilityNodeInfo accessibilityNodeInfo = (node == null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) ? new AccessibilityNodeInfo() @@ -151,36 +150,26 @@ public AccessibleUiObject findObject(Object selector) throws UiAutomator2Excepti return node == null ? null : new AccessibleUiObject(toUiObject2(realSelector, node), node); } - public synchronized GestureController getGestureController() { - if (gestureController == null) { - Class gesturesClass = ReflectionUtils.getClass("androidx.test.uiautomator.Gestures"); - // TODO: UIAutomator lib has changed this class significantly in v2.3.0, - // TODO: so this approach won't work anymore - Method gesturesFactory = ReflectionUtils.getMethod( - gesturesClass, "getInstance", UiDevice.class - ); - Gestures gestures; - try { - gestures = new Gestures(gesturesFactory.invoke(gesturesClass, getUiDevice())); - } catch (InvocationTargetException | IllegalAccessException e) { - throw new UiAutomator2Exception("Cannot get an instance of the Gestures class", e); - } - Class gestureControllerClass = ReflectionUtils.getClass( - "androidx.test.uiautomator.GestureController" - ); - Method gestureControllerFactory = ReflectionUtils.getMethod( - gestureControllerClass, "getInstance", UiDevice.class - ); - try { - gestureController = new GestureController( - gestureControllerFactory.invoke(gestureControllerClass, getUiDevice()), - gestures - ); - } catch (InvocationTargetException | IllegalAccessException e) { - throw new UiAutomator2Exception("Cannot get an instance of the GestureController class", e); - } + private Object getNativeGestureControllerInstance() { + Class gestureControllerClass = ReflectionUtils.getClass("androidx.test.uiautomator.GestureController"); + Method gestureControllerFactory = ReflectionUtils.getMethod(gestureControllerClass, "getInstance", UiDevice.class); + try { + return gestureControllerFactory.invoke(gestureControllerClass, getUiDevice()); + } catch (InvocationTargetException | IllegalAccessException e) { + throw new UiAutomator2Exception("Cannot get an instance of the GestureController class", e); } - return gestureController; + } + + public GestureController getGestureController(int displayId) { + return new GestureController(nativeGestureController, displayId); + } + + public GestureController getGestureController() { + return new GestureController(nativeGestureController); + } + + public GestureController getGestureController(AndroidElement element) { + return getGestureController(element.getDisplayId()); } /** diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java index 1d00b9d52..6b733f8d9 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java @@ -21,6 +21,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.SystemClock; +import android.view.Display; import android.view.ViewConfiguration; import androidx.annotation.Nullable; @@ -35,15 +36,28 @@ import static io.appium.uiautomator2.utils.ReflectionUtils.invoke; public class GestureController { - private final Object wrappedInstance; private final Method performGestureMethod; private final Gestures gestures; - GestureController(Object wrappedInstance, Gestures gestures) { + GestureController(Object wrappedInstance, int displayId) { this.wrappedInstance = wrappedInstance; this.performGestureMethod = extractPerformGestureMethod(wrappedInstance); - this.gestures = gestures; + this.gestures = new Gestures(displayId); + } + + GestureController(Object wrappedInstance) { + this(wrappedInstance, getCurrentDisplayId()); + } + + private static int getCurrentDisplayId() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + Display display = getInstrumentation().getTargetContext().getDisplay(); + if (display != null) { + return display.getDisplayId(); + } + } + return Display.DEFAULT_DISPLAY; } private static Method extractPerformGestureMethod(Object wrappedInstance) { @@ -70,7 +84,7 @@ private static UiDevice getDevice() { } private class GestureRunnable implements Runnable { - private PointerGesture[] mGestures; + private final PointerGesture[] mGestures; public GestureRunnable(PointerGesture[] gestures) { mGestures = gestures; diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java b/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java index 4fd42d0a8..5a6d6ec77 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java @@ -20,7 +20,6 @@ import android.graphics.Point; import android.graphics.Rect; - import androidx.test.uiautomator.Direction; import androidx.test.uiautomator.UiObject2; @@ -33,43 +32,62 @@ import static io.appium.uiautomator2.utils.ReflectionUtils.getMethod; import static io.appium.uiautomator2.utils.ReflectionUtils.invoke; +import io.appium.uiautomator2.utils.ReflectionUtils; + public class Gestures { - private final Object wrappedInstance; + private final Class wrappedClass; + private final int displayId; - Gestures(Object wrappedInstance) { - this.wrappedInstance = wrappedInstance; + Gestures(int displayId) { + this.displayId = displayId; + // https://androidx.tech/artifacts/test.uiautomator/uiautomator/2.3.0-beta01-source/androidx/test/uiautomator/Gestures.java.html + this.wrappedClass = ReflectionUtils.getClass("androidx.test.uiautomator.Gestures"); } public PointerGesture drag(Point start, Point end, int speed) { - Method dragMethod = getMethod(wrappedInstance.getClass(), "drag", - Point.class, Point.class, int.class); - return new PointerGesture(invoke(dragMethod, wrappedInstance, start, end, speed)); - } - - private static PointerGesture[] toGesturesArray(Object result) { - List list = new ArrayList<>(); - for (int i = 0; i < Array.getLength(result); ++i) { - list.add(new PointerGesture(Array.get(result, i))); - } - return list.toArray(new PointerGesture[0]); + Method dragMethod = getMethod( + wrappedClass, "drag", + Point.class, Point.class, int.class, int.class + ); + return new PointerGesture(invoke(dragMethod, wrappedClass, start, end, speed, displayId)); } public PointerGesture[] pinchClose(Rect area, float percent, int speed) { - Method pinchCloseMethod = getMethod(wrappedInstance.getClass(), "pinchClose", - Rect.class, float.class, int.class); - return toGesturesArray(invoke(pinchCloseMethod, wrappedInstance, area, percent, speed)); + Method pinchCloseMethod = getMethod( + wrappedClass, "pinchClose", + Rect.class, float.class, int.class, int.class + ); + return toGesturesArray( + invoke(pinchCloseMethod, wrappedClass, area, percent, speed, displayId) + ); } public PointerGesture[] pinchOpen(Rect area, float percent, int speed) { - Method pinchOpenMethod = getMethod(wrappedInstance.getClass(), "pinchOpen", - Rect.class, float.class, int.class); - return toGesturesArray(invoke(pinchOpenMethod, wrappedInstance, area, percent, speed)); + Method pinchOpenMethod = getMethod( + wrappedClass, "pinchOpen", + Rect.class, float.class, int.class, int.class + ); + return toGesturesArray( + invoke(pinchOpenMethod, wrappedClass, area, percent, speed, displayId) + ); } public PointerGesture swipe(Rect area, Direction direction, float percent, int speed) { - Method swipeRectMethod = getMethod(wrappedInstance.getClass(), "swipeRect", - Rect.class, Direction.class, float.class, int.class); - return new PointerGesture(invoke(swipeRectMethod, wrappedInstance, area, direction, percent, speed)); + Method swipeRectMethod = getMethod( + wrappedClass, "swipeRect", + Rect.class, Direction.class, float.class, int.class, int.class + ); + return new PointerGesture( + invoke(swipeRectMethod, wrappedClass, area, direction, percent, speed, displayId) + ); + } + + private static PointerGesture[] toGesturesArray(Object result) { + List list = new ArrayList<>(); + for (int i = 0; i < Array.getLength(result); ++i) { + list.add(new PointerGesture(Array.get(result, i))); + } + return list.toArray(new PointerGesture[0]); } public static float getDisplayDensity() { diff --git a/app/src/main/java/io/appium/uiautomator2/utils/gestures/Click.java b/app/src/main/java/io/appium/uiautomator2/utils/gestures/Click.java index 07950e563..70ad47c18 100644 --- a/app/src/main/java/io/appium/uiautomator2/utils/gestures/Click.java +++ b/app/src/main/java/io/appium/uiautomator2/utils/gestures/Click.java @@ -55,7 +55,7 @@ public static Object perform(ClickModel clickModel) { bounds.left + clickModel.offset.x.intValue(), bounds.top + clickModel.offset.y.intValue() ); - CustomUiDevice.getInstance().getGestureController().click(location); + CustomUiDevice.getInstance().getGestureController(element).click(location); } } return null; diff --git a/app/src/main/java/io/appium/uiautomator2/utils/gestures/DoubleClick.java b/app/src/main/java/io/appium/uiautomator2/utils/gestures/DoubleClick.java index 840e01cc3..3c4defaf7 100644 --- a/app/src/main/java/io/appium/uiautomator2/utils/gestures/DoubleClick.java +++ b/app/src/main/java/io/appium/uiautomator2/utils/gestures/DoubleClick.java @@ -55,7 +55,7 @@ public static Object perform(DoubleClickModel doubleClickModel) { Rect bounds = element.getBounds(); Point location = new Point(bounds.left + doubleClickModel.offset.x.intValue(), bounds.top + doubleClickModel.offset.y.intValue()); - CustomUiDevice.getInstance().getGestureController().doubleClick(location); + CustomUiDevice.getInstance().getGestureController(element).doubleClick(location); } } return null; diff --git a/app/src/main/java/io/appium/uiautomator2/utils/gestures/LongClick.java b/app/src/main/java/io/appium/uiautomator2/utils/gestures/LongClick.java index 47990f4fd..314a5116e 100644 --- a/app/src/main/java/io/appium/uiautomator2/utils/gestures/LongClick.java +++ b/app/src/main/java/io/appium/uiautomator2/utils/gestures/LongClick.java @@ -60,7 +60,7 @@ public static Object perform(LongClickModel longClickModel) { Rect bounds = element.getBounds(); Point location = new Point(bounds.left + longClickModel.offset.x.intValue(), bounds.top + longClickModel.offset.y.intValue()); - CustomUiDevice.getInstance().getGestureController().longClick(location, + CustomUiDevice.getInstance().getGestureController(element).longClick(location, longClickModel.duration == null ? null : longClickModel.duration.longValue() ); } From 955541ec56f9a95cd0f236c6713a6a1bada0996b Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 2 Jan 2024 14:09:37 +0100 Subject: [PATCH 2/6] Fix PointerGesture --- .../model/internal/GestureController.java | 8 ++++---- .../uiautomator2/model/internal/Gestures.java | 16 ++++++++++++---- .../model/internal/PointerGesture.java | 6 +++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java index 6b733f8d9..fbea0d5db 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java @@ -101,13 +101,13 @@ private R performGestureAndWait(EventCondition condition, long timeout, P } public void click(Point point) { - performGesture(new PointerGesture(point).pause(0L)); + performGesture(new PointerGesture(point, gestures.getDisplayId()).pause(0L)); } public void doubleClick(Point point) { - performGesture(new PointerGesture(point).pause(0L)); + performGesture(new PointerGesture(point, gestures.getDisplayId()).pause(0L)); SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() / 2); - performGesture(new PointerGesture(point).pause(0L)); + performGesture(new PointerGesture(point, gestures.getDisplayId()).pause(0L)); } public void longClick(Point point, @Nullable Long durationMs) { @@ -115,7 +115,7 @@ public void longClick(Point point, @Nullable Long durationMs) { if (duration < 0) { throw new IllegalArgumentException("Long click duration cannot be negative"); } - performGesture(new PointerGesture(point).pause(duration)); + performGesture(new PointerGesture(point, gestures.getDisplayId()).pause(duration)); } private static int checkSpeed(int speed) { diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java b/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java index 5a6d6ec77..e64c89366 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/Gestures.java @@ -44,12 +44,19 @@ public class Gestures { this.wrappedClass = ReflectionUtils.getClass("androidx.test.uiautomator.Gestures"); } + public int getDisplayId() { + return displayId; + } + public PointerGesture drag(Point start, Point end, int speed) { Method dragMethod = getMethod( wrappedClass, "drag", Point.class, Point.class, int.class, int.class ); - return new PointerGesture(invoke(dragMethod, wrappedClass, start, end, speed, displayId)); + return new PointerGesture( + invoke(dragMethod, wrappedClass, start, end, speed, displayId), + displayId + ); } public PointerGesture[] pinchClose(Rect area, float percent, int speed) { @@ -78,14 +85,15 @@ public PointerGesture swipe(Rect area, Direction direction, float percent, int s Rect.class, Direction.class, float.class, int.class, int.class ); return new PointerGesture( - invoke(swipeRectMethod, wrappedClass, area, direction, percent, speed, displayId) + invoke(swipeRectMethod, wrappedClass, area, direction, percent, speed, displayId), + displayId ); } - private static PointerGesture[] toGesturesArray(Object result) { + private PointerGesture[] toGesturesArray(Object result) { List list = new ArrayList<>(); for (int i = 0; i < Array.getLength(result); ++i) { - list.add(new PointerGesture(Array.get(result, i))); + list.add(new PointerGesture(Array.get(result, i), displayId)); } return list.toArray(new PointerGesture[0]); } diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java b/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java index d1b1cb729..c895eb7ea 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java @@ -43,15 +43,15 @@ public synchronized static Class getWrappedClass() { private static synchronized Constructor getWrappedConstructor() { if (pointerGestureConstructor == null) { - pointerGestureConstructor = getConstructor(getWrappedClass(), Point.class); + pointerGestureConstructor = getConstructor(getWrappedClass(), Point.class, int.class); } return pointerGestureConstructor; } - public PointerGesture(Object wrappedInstanceOrPoint) { + public PointerGesture(Object wrappedInstanceOrPoint, int displayId) { if (wrappedInstanceOrPoint instanceof Point) { try { - this.wrappedInstance = getWrappedConstructor().newInstance(wrappedInstanceOrPoint); + this.wrappedInstance = getWrappedConstructor().newInstance(wrappedInstanceOrPoint, displayId); } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { throw new IllegalStateException(String.format("Cannot perform gesture at %s", wrappedInstanceOrPoint), e); } From 0fc737114498de0a1fbd2387dc9cc23be47697e7 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 2 Jan 2024 17:06:50 +0100 Subject: [PATCH 3/6] Avoid NPE --- .../unittest/test/ActionsCommandsTest.java | 346 +++++++++--------- .../uiautomator2/core/AxNodeInfoHelper.java | 175 +++++---- .../model/internal/CustomUiDevice.java | 25 ++ .../model/internal/GestureController.java | 26 +- .../model/internal/PointerGesture.java | 5 + 5 files changed, 316 insertions(+), 261 deletions(-) diff --git a/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java b/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java index 554cae4fc..a6ed07195 100644 --- a/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java +++ b/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java @@ -88,91 +88,91 @@ private void setupEditView() throws JSONException { clickAndWaitForStaleness(response.getElementId()); } - @Test - public void verifyDragAndDropOnAnotherElement() throws JSONException { - setupDragDropView(); - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response dot2Response = findElement(By.id(dotIdByIdx(2))); - final JSONArray actionsJson = new JSONArray(String.format("[ {" + - "\"type\": \"pointer\"," + - "\"id\": \"finger1\"," + - "\"parameters\": {\"pointerType\": \"touch\"}," + - "\"actions\": [" + - "{\"type\": \"pointerMove\", \"duration\": 0, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + - "{\"type\": \"pointerDown\"}," + - "{\"type\": \"pause\", \"duration\": 1000}," + - "{\"type\": \"pointerMove\", \"duration\": 3000, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + - "{\"type\": \"pointerUp\"}]" + - "} ]", dot1Response.getElementId(), dot2Response.getElementId())); - Response actionsResponse = performActions(actionsJson); - assertTrue(actionsResponse.isSuccessful()); - verifyDragResult(); - } - - @Test - public void verifyTypingText() throws JSONException { - setupEditView(); - - Response edit = findElement(By.id("io.appium.android.apis:id/username_edit")); - click(edit.getElementId()); - final JSONArray actionsJson = new JSONArray("[ {" + - "\"type\": \"key\"," + - "\"id\": \"keyboard\"," + - "\"actions\": [" + - "{\"type\": \"keyDown\", \"value\": \"h\"}," + - "{\"type\": \"keyUp\", \"value\": \"h\"}," + - "{\"type\": \"keyDown\", \"value\": \"i\"}," + - "{\"type\": \"keyUp\", \"value\": \"i\"}]" + - "} ]"); - Response actionsResponse = performActions(actionsJson); - assertTrue(actionsResponse.isSuccessful()); - Response response = getText(edit.getElementId()); - assertThat((String) response.getValue(), equalTo("hi")); - } - - @Test - public void verifyLongClickGesture() throws JSONException { - setupDragDropView(); - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response longClickResponse = longClick(dot1Response.getElementId(), null, null); - assertTrue(longClickResponse.isSuccessful()); - longClickResponse = longClick(dot1Response.getElementId(), new Point(1,1), null); - assertTrue(longClickResponse.isSuccessful()); - longClickResponse = longClick(null, new Point(200,200), 2000L); - assertTrue(longClickResponse.isSuccessful()); - // negative - longClickResponse = longClick(null, null, 2000L); - assertFalse(longClickResponse.isSuccessful()); - longClickResponse = longClick(dot1Response.getElementId(), null, -1L); - assertFalse(longClickResponse.isSuccessful()); - } - - @Test - public void verifyDragGesture() throws JSONException { - setupDragDropView(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // TODO: The test is unstable on API30 - return; - } - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), null); - assertTrue(dragResponse.isSuccessful()); - dragResponse = drag(null, new Point(200,200), new Point(1,1), null); - assertTrue(dragResponse.isSuccessful()); - dragResponse = drag(null, new Point(200,200), new Point(1,1), 200); - assertTrue(dragResponse.isSuccessful()); - // negative - dragResponse = drag(null, new Point(200,200), null, 200); - assertFalse(dragResponse.isSuccessful()); - dragResponse = drag(null, null, new Point(1,1), 200); - assertFalse(dragResponse.isSuccessful()); - dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), -1); - assertFalse(dragResponse.isSuccessful()); - } +// @Test +// public void verifyDragAndDropOnAnotherElement() throws JSONException { +// setupDragDropView(); +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response dot2Response = findElement(By.id(dotIdByIdx(2))); +// final JSONArray actionsJson = new JSONArray(String.format("[ {" + +// "\"type\": \"pointer\"," + +// "\"id\": \"finger1\"," + +// "\"parameters\": {\"pointerType\": \"touch\"}," + +// "\"actions\": [" + +// "{\"type\": \"pointerMove\", \"duration\": 0, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + +// "{\"type\": \"pointerDown\"}," + +// "{\"type\": \"pause\", \"duration\": 1000}," + +// "{\"type\": \"pointerMove\", \"duration\": 3000, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + +// "{\"type\": \"pointerUp\"}]" + +// "} ]", dot1Response.getElementId(), dot2Response.getElementId())); +// Response actionsResponse = performActions(actionsJson); +// assertTrue(actionsResponse.isSuccessful()); +// verifyDragResult(); +// } +// +// @Test +// public void verifyTypingText() throws JSONException { +// setupEditView(); +// +// Response edit = findElement(By.id("io.appium.android.apis:id/username_edit")); +// click(edit.getElementId()); +// final JSONArray actionsJson = new JSONArray("[ {" + +// "\"type\": \"key\"," + +// "\"id\": \"keyboard\"," + +// "\"actions\": [" + +// "{\"type\": \"keyDown\", \"value\": \"h\"}," + +// "{\"type\": \"keyUp\", \"value\": \"h\"}," + +// "{\"type\": \"keyDown\", \"value\": \"i\"}," + +// "{\"type\": \"keyUp\", \"value\": \"i\"}]" + +// "} ]"); +// Response actionsResponse = performActions(actionsJson); +// assertTrue(actionsResponse.isSuccessful()); +// Response response = getText(edit.getElementId()); +// assertThat((String) response.getValue(), equalTo("hi")); +// } +// +// @Test +// public void verifyLongClickGesture() throws JSONException { +// setupDragDropView(); +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response longClickResponse = longClick(dot1Response.getElementId(), null, null); +// assertTrue(longClickResponse.isSuccessful()); +// longClickResponse = longClick(dot1Response.getElementId(), new Point(1,1), null); +// assertTrue(longClickResponse.isSuccessful()); +// longClickResponse = longClick(null, new Point(200,200), 2000L); +// assertTrue(longClickResponse.isSuccessful()); +// // negative +// longClickResponse = longClick(null, null, 2000L); +// assertFalse(longClickResponse.isSuccessful()); +// longClickResponse = longClick(dot1Response.getElementId(), null, -1L); +// assertFalse(longClickResponse.isSuccessful()); +// } +// +// @Test +// public void verifyDragGesture() throws JSONException { +// setupDragDropView(); +// +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { +// // TODO: The test is unstable on API30 +// return; +// } +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), null); +// assertTrue(dragResponse.isSuccessful()); +// dragResponse = drag(null, new Point(200,200), new Point(1,1), null); +// assertTrue(dragResponse.isSuccessful()); +// dragResponse = drag(null, new Point(200,200), new Point(1,1), 200); +// assertTrue(dragResponse.isSuccessful()); +// // negative +// dragResponse = drag(null, new Point(200,200), null, 200); +// assertFalse(dragResponse.isSuccessful()); +// dragResponse = drag(null, null, new Point(1,1), 200); +// assertFalse(dragResponse.isSuccessful()); +// dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), -1); +// assertFalse(dragResponse.isSuccessful()); +// } @Test public void verifyFlingGesture() throws JSONException { @@ -194,92 +194,92 @@ public void verifyFlingGesture() throws JSONException { assertFalse(flingResponse.isSuccessful()); } - @Test - public void verifyPinchCloseGesture() throws JSONException { - setupDragDropView(); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - // TODO: The test is unstable on API30 - return; - } - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response pinchCloseResponse = pinchClose(dot1Response.getElementId(), null, 0.5f, null); - assertTrue(pinchCloseResponse.isSuccessful()); - pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, null); - assertTrue(pinchCloseResponse.isSuccessful()); - pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, 1000); - assertTrue(pinchCloseResponse.isSuccessful()); - // negative - pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), -1f, null); - assertFalse(pinchCloseResponse.isSuccessful()); - pinchCloseResponse = pinchClose(null, null, 0.5f, null); - assertFalse(pinchCloseResponse.isSuccessful()); - pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, -1); - assertFalse(pinchCloseResponse.isSuccessful()); - } - - @Test - public void verifyPinchOpenGesture() throws JSONException { - setupDragDropView(); - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response pinchOpenResponse = pinchOpen(dot1Response.getElementId(), null, 0.5f, null); - assertTrue(pinchOpenResponse.isSuccessful()); - pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, null); - assertTrue(pinchOpenResponse.isSuccessful()); - pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, 1000); - assertTrue(pinchOpenResponse.isSuccessful()); - // negative - pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), -1f, null); - assertFalse(pinchOpenResponse.isSuccessful()); - pinchOpenResponse = pinchOpen(null, null, 0.5f, null); - assertFalse(pinchOpenResponse.isSuccessful()); - pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, -1); - assertFalse(pinchOpenResponse.isSuccessful()); - } - - @Test - public void verifyScrollGesture() throws JSONException { - setupDragDropView(); - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "left", null); - assertTrue(scrollResponse.isSuccessful()); - scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); - assertTrue(scrollResponse.isSuccessful()); - scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); - assertTrue(scrollResponse.isSuccessful()); - // negative - scrollResponse = scroll(null, new Rect(200, 200, 300, 300), -1f, "up", null); - assertFalse(scrollResponse.isSuccessful()); - scrollResponse = scroll(null, null, 0.5f, "up",null); - assertFalse(scrollResponse.isSuccessful()); - scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); - assertFalse(scrollResponse.isSuccessful()); - scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "foo", null); - assertFalse(scrollResponse.isSuccessful()); - } - - @Test - public void verifySwipeGesture() throws JSONException { - setupDragDropView(); - - Response dot1Response = findElement(By.id(dotIdByIdx(1))); - Response swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "left", null); - assertTrue(swipeResponse.isSuccessful()); - swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); - assertTrue(swipeResponse.isSuccessful()); - swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); - assertTrue(swipeResponse.isSuccessful()); - // negative - swipeResponse = swipe(null, new Rect(200, 200, 300, 300), -1f, "up", null); - assertFalse(swipeResponse.isSuccessful()); - swipeResponse = swipe(null, null, 0.5f, "up",null); - assertFalse(swipeResponse.isSuccessful()); - swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); - assertFalse(swipeResponse.isSuccessful()); - swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "foo", null); - assertFalse(swipeResponse.isSuccessful()); - } +// @Test +// public void verifyPinchCloseGesture() throws JSONException { +// setupDragDropView(); +// +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { +// // TODO: The test is unstable on API30 +// return; +// } +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response pinchCloseResponse = pinchClose(dot1Response.getElementId(), null, 0.5f, null); +// assertTrue(pinchCloseResponse.isSuccessful()); +// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, null); +// assertTrue(pinchCloseResponse.isSuccessful()); +// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, 1000); +// assertTrue(pinchCloseResponse.isSuccessful()); +// // negative +// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), -1f, null); +// assertFalse(pinchCloseResponse.isSuccessful()); +// pinchCloseResponse = pinchClose(null, null, 0.5f, null); +// assertFalse(pinchCloseResponse.isSuccessful()); +// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, -1); +// assertFalse(pinchCloseResponse.isSuccessful()); +// } +// +// @Test +// public void verifyPinchOpenGesture() throws JSONException { +// setupDragDropView(); +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response pinchOpenResponse = pinchOpen(dot1Response.getElementId(), null, 0.5f, null); +// assertTrue(pinchOpenResponse.isSuccessful()); +// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, null); +// assertTrue(pinchOpenResponse.isSuccessful()); +// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, 1000); +// assertTrue(pinchOpenResponse.isSuccessful()); +// // negative +// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), -1f, null); +// assertFalse(pinchOpenResponse.isSuccessful()); +// pinchOpenResponse = pinchOpen(null, null, 0.5f, null); +// assertFalse(pinchOpenResponse.isSuccessful()); +// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, -1); +// assertFalse(pinchOpenResponse.isSuccessful()); +// } +// +// @Test +// public void verifyScrollGesture() throws JSONException { +// setupDragDropView(); +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "left", null); +// assertTrue(scrollResponse.isSuccessful()); +// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); +// assertTrue(scrollResponse.isSuccessful()); +// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); +// assertTrue(scrollResponse.isSuccessful()); +// // negative +// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), -1f, "up", null); +// assertFalse(scrollResponse.isSuccessful()); +// scrollResponse = scroll(null, null, 0.5f, "up",null); +// assertFalse(scrollResponse.isSuccessful()); +// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); +// assertFalse(scrollResponse.isSuccessful()); +// scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "foo", null); +// assertFalse(scrollResponse.isSuccessful()); +// } +// +// @Test +// public void verifySwipeGesture() throws JSONException { +// setupDragDropView(); +// +// Response dot1Response = findElement(By.id(dotIdByIdx(1))); +// Response swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "left", null); +// assertTrue(swipeResponse.isSuccessful()); +// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); +// assertTrue(swipeResponse.isSuccessful()); +// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); +// assertTrue(swipeResponse.isSuccessful()); +// // negative +// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), -1f, "up", null); +// assertFalse(swipeResponse.isSuccessful()); +// swipeResponse = swipe(null, null, 0.5f, "up",null); +// assertFalse(swipeResponse.isSuccessful()); +// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); +// assertFalse(swipeResponse.isSuccessful()); +// swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "foo", null); +// assertFalse(swipeResponse.isSuccessful()); +// } } diff --git a/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java b/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java index 1dd54cbad..e695cc0e2 100644 --- a/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java +++ b/app/src/main/java/io/appium/uiautomator2/core/AxNodeInfoHelper.java @@ -30,10 +30,6 @@ 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; @@ -43,7 +39,6 @@ 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; @@ -55,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) { @@ -116,16 +118,7 @@ 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) { @@ -170,7 +163,7 @@ private static GestureController makeGestureController(AccessibilityNodeInfo nod } public static int getAxNodeDisplayId(AccessibilityNodeInfo node) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (node != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { AccessibilityWindowInfo window = node.getWindow(); if (window != null) { return window.getDisplayId(); @@ -192,7 +185,9 @@ public static void swipe(AccessibilityNodeInfo node, Direction direction, float 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); makeGestureController(node).swipe(bounds, direction, percent, speed); } @@ -201,7 +196,9 @@ public static boolean scroll(AccessibilityNodeInfo node, Direction direction, fl 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 makeGestureController(node).scroll(bounds, direction, percent, speed); } @@ -210,29 +207,72 @@ 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 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); - UiDevice uiDevice = getUiDevice(); - Rect screenRect = new Rect(0, 0, uiDevice.getDisplayWidth(), uiDevice.getDisplayHeight()); - return getBounds(node, screenRect, 0); + // 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); + } + + // 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) { @@ -248,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 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 * @@ -346,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)); + } + } } diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java index d0f719c38..099331b15 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java @@ -18,8 +18,10 @@ import android.app.Instrumentation; import android.app.UiAutomation; +import android.graphics.Point; import android.os.Build; import android.os.SystemClock; +import android.view.Display; import android.view.accessibility.AccessibilityNodeInfo; import androidx.annotation.NonNull; @@ -215,4 +217,27 @@ public ScreenRotation setRotationSync(ScreenRotation desired) { throw new InvalidElementStateException(String.format("Screen rotation cannot be changed to %s after %sms. " + "Is it locked programmatically?", desired, CHANGE_ROTATION_TIMEOUT_MS)); } + + @Nullable + public Display getDisplayById(int displayId) { + Method getDisplayByIdMethod = ReflectionUtils.getMethod( + UiDevice.class, "getDisplayById", int.class + ); + try { + return (Display) getDisplayByIdMethod.invoke(getUiDevice(), displayId); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new UiAutomator2Exception(e); + } + } + + public Point getDisplaySize(int displayId) { + Method getDisplaySizeMethod = ReflectionUtils.getMethod( + UiDevice.class, "getDisplaySize", int.class + ); + try { + return (Point) getDisplaySizeMethod.invoke(getUiDevice(), displayId); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new UiAutomator2Exception(e); + } + } } diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java index fbea0d5db..15edfba00 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java @@ -32,6 +32,7 @@ import java.lang.reflect.Array; import java.lang.reflect.Method; +import java.util.Arrays; import static io.appium.uiautomator2.utils.ReflectionUtils.invoke; @@ -94,6 +95,11 @@ public GestureRunnable(PointerGesture[] gestures) { public void run() { performGesture(mGestures); } + + @Override + public String toString() { + return Arrays.toString(mGestures); + } } private R performGestureAndWait(EventCondition condition, long timeout, PointerGesture... gestures) { @@ -159,17 +165,17 @@ public boolean scroll(Rect area, Direction direction, float percent, @Nullable I Direction swipeDirection = Direction.reverse(direction); int scrollSpeed = speed == null ? Gestures.getDefaultScrollSpeed() : checkSpeed(speed); - float swipePercent = percent; - while (swipePercent > 0.0f) { + for (float swipePercent = percent; swipePercent > 0.0f; swipePercent -= 1.0f) { float segment = Math.min(swipePercent, 1.0f); PointerGesture swipe = gestures.swipe(area, swipeDirection, segment, scrollSpeed).pause(250); // Perform the gesture and return early if we reached the end - if (performGestureAndWait(Until.scrollFinished(direction), Gestures.getScrollTimeout(), swipe)) { + Boolean scrollFinishedResult = performGestureAndWait( + Until.scrollFinished(direction), Gestures.getScrollTimeout(), swipe + ); + if (!Boolean.FALSE.equals(scrollFinishedResult)) { return false; } - - swipePercent -= 1.0f; } // We never reached the end return true; @@ -181,7 +187,8 @@ public boolean fling(Rect area, Direction direction, @Nullable Integer speed) { int flingSpeed = speed == null ? Gestures.getDefaultFlingSpeed() : speed; if (flingSpeed < minVelocity) { throw new IllegalArgumentException(String.format( - "Speed %s is less than the minimum fling velocity %s", speed, minVelocity)); + "Speed %s is less than the minimum fling velocity %s", speed, minVelocity) + ); } // To fling, we swipe in the opposite direction @@ -189,6 +196,11 @@ public boolean fling(Rect area, Direction direction, @Nullable Integer speed) { PointerGesture swipe = gestures.swipe(area, swipeDirection, 1.0f, flingSpeed); // Perform the gesture and return true if we did not reach the end - return !performGestureAndWait(Until.scrollFinished(direction), Gestures.getFlingTimeout(), swipe); + Boolean scrollFinishedResult = performGestureAndWait( + Until.scrollFinished(direction), + Gestures.getFlingTimeout(), + swipe + ); + return Boolean.FALSE.equals(scrollFinishedResult); } } diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java b/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java index c895eb7ea..070bbc197 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/PointerGesture.java @@ -69,4 +69,9 @@ public PointerGesture pause(long ms) { public Object getWrappedInstance() { return this.wrappedInstance; } + + @Override + public String toString() { + return wrappedInstance.toString(); + } } From 57c7e23f6adb3ad0fd2bd60ea797aacfc1e1e55f Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Tue, 2 Jan 2024 17:53:25 +0100 Subject: [PATCH 4/6] fix window retrieval --- .../model/internal/CustomUiDevice.java | 17 +++++++++++++++++ .../model/internal/GestureController.java | 13 +------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java index 099331b15..6c72c25a2 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java @@ -21,8 +21,10 @@ import android.graphics.Point; import android.os.Build; import android.os.SystemClock; +import android.util.SparseArray; import android.view.Display; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -240,4 +242,19 @@ public Point getDisplaySize(int displayId) { throw new UiAutomator2Exception(e); } } + + public int getTopmostWindowDisplayId() { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { + SparseArray> windows = getUiAutomation().getWindowsOnAllDisplays(); + for (int i = 0; i < windows.size(); i++) { + int key = windows.keyAt(i); + List windowsList = windows.get(key); + if (windowsList.isEmpty()) { + continue; + } + return windowsList.get(0).getDisplayId(); + } + } + return Display.DEFAULT_DISPLAY; + } } diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java index 15edfba00..b824df84f 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/GestureController.java @@ -21,7 +21,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.SystemClock; -import android.view.Display; import android.view.ViewConfiguration; import androidx.annotation.Nullable; @@ -48,17 +47,7 @@ public class GestureController { } GestureController(Object wrappedInstance) { - this(wrappedInstance, getCurrentDisplayId()); - } - - private static int getCurrentDisplayId() { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { - Display display = getInstrumentation().getTargetContext().getDisplay(); - if (display != null) { - return display.getDisplayId(); - } - } - return Display.DEFAULT_DISPLAY; + this(wrappedInstance, CustomUiDevice.getInstance().getTopmostWindowDisplayId()); } private static Method extractPerformGestureMethod(Object wrappedInstance) { From 999fc63a9d8dd3b84886895654ce8aa502c0ff4e Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 3 Jan 2024 04:44:21 +0100 Subject: [PATCH 5/6] simplify --- .../uiautomator2/model/internal/CustomUiDevice.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java index 6c72c25a2..4707644fe 100644 --- a/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java +++ b/app/src/main/java/io/appium/uiautomator2/model/internal/CustomUiDevice.java @@ -245,14 +245,12 @@ public Point getDisplaySize(int displayId) { public int getTopmostWindowDisplayId() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R) { - SparseArray> windows = getUiAutomation().getWindowsOnAllDisplays(); - for (int i = 0; i < windows.size(); i++) { - int key = windows.keyAt(i); - List windowsList = windows.get(key); - if (windowsList.isEmpty()) { - continue; + SparseArray> windowsMap = getUiAutomation().getWindowsOnAllDisplays(); + for (int i = 0; i < windowsMap.size(); i++) { + int displayId = windowsMap.keyAt(i); + if (displayId >= 0) { + return displayId; } - return windowsList.get(0).getDisplayId(); } } return Display.DEFAULT_DISPLAY; From 9a486b33b57716b3fc05b5eeb982d07299b109be Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Wed, 3 Jan 2024 05:21:15 +0100 Subject: [PATCH 6/6] tests --- .../unittest/test/ActionsCommandsTest.java | 346 +++++++++--------- 1 file changed, 173 insertions(+), 173 deletions(-) diff --git a/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java b/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java index a6ed07195..554cae4fc 100644 --- a/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java +++ b/app/src/androidTestE2eTest/java/io/appium/uiautomator2/unittest/test/ActionsCommandsTest.java @@ -88,91 +88,91 @@ private void setupEditView() throws JSONException { clickAndWaitForStaleness(response.getElementId()); } -// @Test -// public void verifyDragAndDropOnAnotherElement() throws JSONException { -// setupDragDropView(); -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response dot2Response = findElement(By.id(dotIdByIdx(2))); -// final JSONArray actionsJson = new JSONArray(String.format("[ {" + -// "\"type\": \"pointer\"," + -// "\"id\": \"finger1\"," + -// "\"parameters\": {\"pointerType\": \"touch\"}," + -// "\"actions\": [" + -// "{\"type\": \"pointerMove\", \"duration\": 0, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + -// "{\"type\": \"pointerDown\"}," + -// "{\"type\": \"pause\", \"duration\": 1000}," + -// "{\"type\": \"pointerMove\", \"duration\": 3000, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + -// "{\"type\": \"pointerUp\"}]" + -// "} ]", dot1Response.getElementId(), dot2Response.getElementId())); -// Response actionsResponse = performActions(actionsJson); -// assertTrue(actionsResponse.isSuccessful()); -// verifyDragResult(); -// } -// -// @Test -// public void verifyTypingText() throws JSONException { -// setupEditView(); -// -// Response edit = findElement(By.id("io.appium.android.apis:id/username_edit")); -// click(edit.getElementId()); -// final JSONArray actionsJson = new JSONArray("[ {" + -// "\"type\": \"key\"," + -// "\"id\": \"keyboard\"," + -// "\"actions\": [" + -// "{\"type\": \"keyDown\", \"value\": \"h\"}," + -// "{\"type\": \"keyUp\", \"value\": \"h\"}," + -// "{\"type\": \"keyDown\", \"value\": \"i\"}," + -// "{\"type\": \"keyUp\", \"value\": \"i\"}]" + -// "} ]"); -// Response actionsResponse = performActions(actionsJson); -// assertTrue(actionsResponse.isSuccessful()); -// Response response = getText(edit.getElementId()); -// assertThat((String) response.getValue(), equalTo("hi")); -// } -// -// @Test -// public void verifyLongClickGesture() throws JSONException { -// setupDragDropView(); -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response longClickResponse = longClick(dot1Response.getElementId(), null, null); -// assertTrue(longClickResponse.isSuccessful()); -// longClickResponse = longClick(dot1Response.getElementId(), new Point(1,1), null); -// assertTrue(longClickResponse.isSuccessful()); -// longClickResponse = longClick(null, new Point(200,200), 2000L); -// assertTrue(longClickResponse.isSuccessful()); -// // negative -// longClickResponse = longClick(null, null, 2000L); -// assertFalse(longClickResponse.isSuccessful()); -// longClickResponse = longClick(dot1Response.getElementId(), null, -1L); -// assertFalse(longClickResponse.isSuccessful()); -// } -// -// @Test -// public void verifyDragGesture() throws JSONException { -// setupDragDropView(); -// -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { -// // TODO: The test is unstable on API30 -// return; -// } -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), null); -// assertTrue(dragResponse.isSuccessful()); -// dragResponse = drag(null, new Point(200,200), new Point(1,1), null); -// assertTrue(dragResponse.isSuccessful()); -// dragResponse = drag(null, new Point(200,200), new Point(1,1), 200); -// assertTrue(dragResponse.isSuccessful()); -// // negative -// dragResponse = drag(null, new Point(200,200), null, 200); -// assertFalse(dragResponse.isSuccessful()); -// dragResponse = drag(null, null, new Point(1,1), 200); -// assertFalse(dragResponse.isSuccessful()); -// dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), -1); -// assertFalse(dragResponse.isSuccessful()); -// } + @Test + public void verifyDragAndDropOnAnotherElement() throws JSONException { + setupDragDropView(); + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response dot2Response = findElement(By.id(dotIdByIdx(2))); + final JSONArray actionsJson = new JSONArray(String.format("[ {" + + "\"type\": \"pointer\"," + + "\"id\": \"finger1\"," + + "\"parameters\": {\"pointerType\": \"touch\"}," + + "\"actions\": [" + + "{\"type\": \"pointerMove\", \"duration\": 0, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + + "{\"type\": \"pointerDown\"}," + + "{\"type\": \"pause\", \"duration\": 1000}," + + "{\"type\": \"pointerMove\", \"duration\": 3000, \"origin\": \"%s\", \"x\": 0, \"y\": 0}," + + "{\"type\": \"pointerUp\"}]" + + "} ]", dot1Response.getElementId(), dot2Response.getElementId())); + Response actionsResponse = performActions(actionsJson); + assertTrue(actionsResponse.isSuccessful()); + verifyDragResult(); + } + + @Test + public void verifyTypingText() throws JSONException { + setupEditView(); + + Response edit = findElement(By.id("io.appium.android.apis:id/username_edit")); + click(edit.getElementId()); + final JSONArray actionsJson = new JSONArray("[ {" + + "\"type\": \"key\"," + + "\"id\": \"keyboard\"," + + "\"actions\": [" + + "{\"type\": \"keyDown\", \"value\": \"h\"}," + + "{\"type\": \"keyUp\", \"value\": \"h\"}," + + "{\"type\": \"keyDown\", \"value\": \"i\"}," + + "{\"type\": \"keyUp\", \"value\": \"i\"}]" + + "} ]"); + Response actionsResponse = performActions(actionsJson); + assertTrue(actionsResponse.isSuccessful()); + Response response = getText(edit.getElementId()); + assertThat((String) response.getValue(), equalTo("hi")); + } + + @Test + public void verifyLongClickGesture() throws JSONException { + setupDragDropView(); + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response longClickResponse = longClick(dot1Response.getElementId(), null, null); + assertTrue(longClickResponse.isSuccessful()); + longClickResponse = longClick(dot1Response.getElementId(), new Point(1,1), null); + assertTrue(longClickResponse.isSuccessful()); + longClickResponse = longClick(null, new Point(200,200), 2000L); + assertTrue(longClickResponse.isSuccessful()); + // negative + longClickResponse = longClick(null, null, 2000L); + assertFalse(longClickResponse.isSuccessful()); + longClickResponse = longClick(dot1Response.getElementId(), null, -1L); + assertFalse(longClickResponse.isSuccessful()); + } + + @Test + public void verifyDragGesture() throws JSONException { + setupDragDropView(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // TODO: The test is unstable on API30 + return; + } + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), null); + assertTrue(dragResponse.isSuccessful()); + dragResponse = drag(null, new Point(200,200), new Point(1,1), null); + assertTrue(dragResponse.isSuccessful()); + dragResponse = drag(null, new Point(200,200), new Point(1,1), 200); + assertTrue(dragResponse.isSuccessful()); + // negative + dragResponse = drag(null, new Point(200,200), null, 200); + assertFalse(dragResponse.isSuccessful()); + dragResponse = drag(null, null, new Point(1,1), 200); + assertFalse(dragResponse.isSuccessful()); + dragResponse = drag(dot1Response.getElementId(), null, new Point(1,1), -1); + assertFalse(dragResponse.isSuccessful()); + } @Test public void verifyFlingGesture() throws JSONException { @@ -194,92 +194,92 @@ public void verifyFlingGesture() throws JSONException { assertFalse(flingResponse.isSuccessful()); } -// @Test -// public void verifyPinchCloseGesture() throws JSONException { -// setupDragDropView(); -// -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { -// // TODO: The test is unstable on API30 -// return; -// } -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response pinchCloseResponse = pinchClose(dot1Response.getElementId(), null, 0.5f, null); -// assertTrue(pinchCloseResponse.isSuccessful()); -// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, null); -// assertTrue(pinchCloseResponse.isSuccessful()); -// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, 1000); -// assertTrue(pinchCloseResponse.isSuccessful()); -// // negative -// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), -1f, null); -// assertFalse(pinchCloseResponse.isSuccessful()); -// pinchCloseResponse = pinchClose(null, null, 0.5f, null); -// assertFalse(pinchCloseResponse.isSuccessful()); -// pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, -1); -// assertFalse(pinchCloseResponse.isSuccessful()); -// } -// -// @Test -// public void verifyPinchOpenGesture() throws JSONException { -// setupDragDropView(); -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response pinchOpenResponse = pinchOpen(dot1Response.getElementId(), null, 0.5f, null); -// assertTrue(pinchOpenResponse.isSuccessful()); -// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, null); -// assertTrue(pinchOpenResponse.isSuccessful()); -// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, 1000); -// assertTrue(pinchOpenResponse.isSuccessful()); -// // negative -// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), -1f, null); -// assertFalse(pinchOpenResponse.isSuccessful()); -// pinchOpenResponse = pinchOpen(null, null, 0.5f, null); -// assertFalse(pinchOpenResponse.isSuccessful()); -// pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, -1); -// assertFalse(pinchOpenResponse.isSuccessful()); -// } -// -// @Test -// public void verifyScrollGesture() throws JSONException { -// setupDragDropView(); -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "left", null); -// assertTrue(scrollResponse.isSuccessful()); -// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); -// assertTrue(scrollResponse.isSuccessful()); -// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); -// assertTrue(scrollResponse.isSuccessful()); -// // negative -// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), -1f, "up", null); -// assertFalse(scrollResponse.isSuccessful()); -// scrollResponse = scroll(null, null, 0.5f, "up",null); -// assertFalse(scrollResponse.isSuccessful()); -// scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); -// assertFalse(scrollResponse.isSuccessful()); -// scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "foo", null); -// assertFalse(scrollResponse.isSuccessful()); -// } -// -// @Test -// public void verifySwipeGesture() throws JSONException { -// setupDragDropView(); -// -// Response dot1Response = findElement(By.id(dotIdByIdx(1))); -// Response swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "left", null); -// assertTrue(swipeResponse.isSuccessful()); -// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); -// assertTrue(swipeResponse.isSuccessful()); -// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); -// assertTrue(swipeResponse.isSuccessful()); -// // negative -// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), -1f, "up", null); -// assertFalse(swipeResponse.isSuccessful()); -// swipeResponse = swipe(null, null, 0.5f, "up",null); -// assertFalse(swipeResponse.isSuccessful()); -// swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); -// assertFalse(swipeResponse.isSuccessful()); -// swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "foo", null); -// assertFalse(swipeResponse.isSuccessful()); -// } + @Test + public void verifyPinchCloseGesture() throws JSONException { + setupDragDropView(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + // TODO: The test is unstable on API30 + return; + } + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response pinchCloseResponse = pinchClose(dot1Response.getElementId(), null, 0.5f, null); + assertTrue(pinchCloseResponse.isSuccessful()); + pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, null); + assertTrue(pinchCloseResponse.isSuccessful()); + pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, 1000); + assertTrue(pinchCloseResponse.isSuccessful()); + // negative + pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), -1f, null); + assertFalse(pinchCloseResponse.isSuccessful()); + pinchCloseResponse = pinchClose(null, null, 0.5f, null); + assertFalse(pinchCloseResponse.isSuccessful()); + pinchCloseResponse = pinchClose(null, new Rect(200, 200, 300, 300), 0.5f, -1); + assertFalse(pinchCloseResponse.isSuccessful()); + } + + @Test + public void verifyPinchOpenGesture() throws JSONException { + setupDragDropView(); + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response pinchOpenResponse = pinchOpen(dot1Response.getElementId(), null, 0.5f, null); + assertTrue(pinchOpenResponse.isSuccessful()); + pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, null); + assertTrue(pinchOpenResponse.isSuccessful()); + pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, 1000); + assertTrue(pinchOpenResponse.isSuccessful()); + // negative + pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), -1f, null); + assertFalse(pinchOpenResponse.isSuccessful()); + pinchOpenResponse = pinchOpen(null, null, 0.5f, null); + assertFalse(pinchOpenResponse.isSuccessful()); + pinchOpenResponse = pinchOpen(null, new Rect(200, 200, 300, 300), 0.5f, -1); + assertFalse(pinchOpenResponse.isSuccessful()); + } + + @Test + public void verifyScrollGesture() throws JSONException { + setupDragDropView(); + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "left", null); + assertTrue(scrollResponse.isSuccessful()); + scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); + assertTrue(scrollResponse.isSuccessful()); + scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); + assertTrue(scrollResponse.isSuccessful()); + // negative + scrollResponse = scroll(null, new Rect(200, 200, 300, 300), -1f, "up", null); + assertFalse(scrollResponse.isSuccessful()); + scrollResponse = scroll(null, null, 0.5f, "up",null); + assertFalse(scrollResponse.isSuccessful()); + scrollResponse = scroll(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); + assertFalse(scrollResponse.isSuccessful()); + scrollResponse = scroll(dot1Response.getElementId(), null, 0.5f, "foo", null); + assertFalse(scrollResponse.isSuccessful()); + } + + @Test + public void verifySwipeGesture() throws JSONException { + setupDragDropView(); + + Response dot1Response = findElement(By.id(dotIdByIdx(1))); + Response swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "left", null); + assertTrue(swipeResponse.isSuccessful()); + swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left", null); + assertTrue(swipeResponse.isSuccessful()); + swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "left",1000); + assertTrue(swipeResponse.isSuccessful()); + // negative + swipeResponse = swipe(null, new Rect(200, 200, 300, 300), -1f, "up", null); + assertFalse(swipeResponse.isSuccessful()); + swipeResponse = swipe(null, null, 0.5f, "up",null); + assertFalse(swipeResponse.isSuccessful()); + swipeResponse = swipe(null, new Rect(200, 200, 300, 300), 0.5f, "up",-1); + assertFalse(swipeResponse.isSuccessful()); + swipeResponse = swipe(dot1Response.getElementId(), null, 0.5f, "foo", null); + assertFalse(swipeResponse.isSuccessful()); + } }