From 10228725a97939dc8fb72499a7a2d52d9366a01f Mon Sep 17 00:00:00 2001
From: Peter Thomas <ptrthomas@gmail.com>
Date: Fri, 28 May 2021 16:59:19 +0530
Subject: [PATCH] [robot] attempt windows scroll pattern #1604

---
 karate-robot/README.md                        | 11 ++++
 .../com/intuit/karate/robot/RobotBase.java    |  4 ++
 .../karate/robot/win/IUIAutomationBase.java   | 17 +++--
 .../robot/win/IUIAutomationScrollPattern.java | 64 +++++++++++++++++++
 .../com/intuit/karate/robot/win/Pattern.java  |  4 +-
 .../intuit/karate/robot/win/ScrollAmount.java | 44 +++++++++++++
 .../intuit/karate/robot/win/WinElement.java   | 48 ++++++++++++++
 7 files changed, 185 insertions(+), 7 deletions(-)
 create mode 100644 karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationScrollPattern.java
 create mode 100644 karate-robot/src/main/java/com/intuit/karate/robot/win/ScrollAmount.java

diff --git a/karate-robot/README.md b/karate-robot/README.md
index 0c8f1f5fa..13461f8bb 100644
--- a/karate-robot/README.md
+++ b/karate-robot/README.md
@@ -77,6 +77,7 @@
     | <a href="#select"><code>select()</code></a>
     | <a href="#highlight"><code>highlight()</code></a>
     | <a href="#highlightall"><code>highlightAll()</code></a>
+    | <a href="#scroll"><code>scroll()</code></a>
   </td>
 </tr>
 <tr>
@@ -757,6 +758,16 @@ Note that you can call this *on* an [`Element`](#element-api) instance if you re
 * locate('//pane{Tree}').screenshot()
 ```
 
+## `scroll()`
+The following methods are available only *on* Windows `Element`-s. Note that they will work only if the "[Scroll Pattern](https://docs.microsoft.com/en-us/windows/win32/api/uiautomationclient/nn-uiautomationclient-iuiautomationscrollpattern)" is available. 
+
+> Note that `scroll()` has not been tested, please contribute if you can. Also refer to the [diff]() as an example of how to add an un-implemented "pattern" to `karate-robot`.
+
+### `scroll(horizontalPercent, verticalPercent)`
+### `scrollUp()`
+### `scrollDown()`
+Both `scrollUp()` and `scrollDown()` take an optional boolean argument to specify if a "large" increment should be used, e.g: `scrollDown(true)`.
+
 ## `screenshotActive()`
 This will screenshot only the [active](#robotactive) control, typically the [window](#window) having focus.
 
diff --git a/karate-robot/src/main/java/com/intuit/karate/robot/RobotBase.java b/karate-robot/src/main/java/com/intuit/karate/robot/RobotBase.java
index b5f60ac86..c34f67529 100644
--- a/karate-robot/src/main/java/com/intuit/karate/robot/RobotBase.java
+++ b/karate-robot/src/main/java/com/intuit/karate/robot/RobotBase.java
@@ -124,6 +124,10 @@ private <T> T get(String key, T defaultValue) {
         return temp == null ? defaultValue : temp;
     }
 
+    public Logger getLogger() {
+        return logger;
+    }        
+
     public RobotBase(ScenarioRuntime runtime) {
         this(runtime, Collections.EMPTY_MAP);
     }
diff --git a/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationBase.java b/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationBase.java
index c6207e716..74587ec51 100644
--- a/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationBase.java
+++ b/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationBase.java
@@ -24,6 +24,7 @@
 package com.intuit.karate.robot.win;
 
 import com.sun.jna.Function;
+import com.sun.jna.ptr.DoubleByReference;
 import com.sun.jna.ptr.IntByReference;
 import com.sun.jna.ptr.PointerByReference;
 import java.util.ArrayList;
@@ -53,10 +54,10 @@ public IUIAutomationBase(PointerByReference ref) {
     protected static int enumValue(String name, String key) {
         return ComUtils.enumValue(name, key);
     }
-    
+
     protected static String enumKey(String name, int value) {
         return ComUtils.enumKey(name, value);
-    }    
+    }
 
     public int invoke(int offset, Object... args) {
         Function function = INTERFACE.getFunction(offset, REF.getValue());
@@ -139,7 +140,7 @@ public IUIAutomationCondition invokeForCondition(String name, Object... args) {
 
     public String invokeForString(String name) {
         ComRef ref = new ComRef();
-        invoke(name, ref);        
+        invoke(name, ref);
         return ref.isNull() ? "" : ref.asString();
     }
 
@@ -148,9 +149,15 @@ public int invokeForInt(String name) {
         invoke(name, ref);
         return ref.getValue();
     }
-    
+
     public boolean invokeForBool(String name) {
         return invokeForInt(name) != 0;
-    }    
+    }
+
+    public double invokeForDouble(String name) {
+        DoubleByReference ref = new DoubleByReference();
+        invoke(name, ref);
+        return ref.getValue();
+    }
 
 }
diff --git a/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationScrollPattern.java b/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationScrollPattern.java
new file mode 100644
index 000000000..4dbddc045
--- /dev/null
+++ b/karate-robot/src/main/java/com/intuit/karate/robot/win/IUIAutomationScrollPattern.java
@@ -0,0 +1,64 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2021 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.robot.win;
+
+/**
+ *
+ * @author pthomas3
+ */
+public class IUIAutomationScrollPattern extends IUIAutomationBase {
+
+    public boolean getCurrentHorizontallyScrollable() {
+        return invokeForBool("CurrentHorizontallyScrollable");
+    }
+
+    public double getCurrentHorizontalScrollPercent() {
+        return invokeForDouble("CurrentHorizontalScrollPercent");
+    }
+
+    public double getCurrentHorizontalViewSize() {
+        return invokeForDouble("CurrentHorizontalViewSize");
+    }
+
+    public boolean getCurrentVerticallyScrollable() {
+        return invokeForBool("CurrentVerticallyScrollable");
+    }
+
+    public double getCurrentVerticalScrollPercent() {
+        return invokeForDouble("CurrentVerticalScrollPercent");
+    }
+
+    public double getCurrentVerticalViewSize() {
+        return invokeForDouble("CurrentVerticalViewSize");
+    }
+
+    public void scroll(ScrollAmount scrollAmount) {
+        invoke("Scroll", scrollAmount.value);
+    }
+
+    public void setScrollPercent(double horizontalPercent, double verticalPercent) {
+        invoke("SetScrollPercent", horizontalPercent, verticalPercent);
+    }
+
+}
diff --git a/karate-robot/src/main/java/com/intuit/karate/robot/win/Pattern.java b/karate-robot/src/main/java/com/intuit/karate/robot/win/Pattern.java
index 07f6f528e..47600d804 100644
--- a/karate-robot/src/main/java/com/intuit/karate/robot/win/Pattern.java
+++ b/karate-robot/src/main/java/com/intuit/karate/robot/win/Pattern.java
@@ -36,7 +36,7 @@ public enum Pattern {
     Selection(10001),
     Value(10002, IUIAutomationValuePattern.class),
     RangeValue(10003),
-    Scroll(10004),
+    Scroll(10004, IUIAutomationScrollPattern.class),
     ExpandCollapse(10005),
     Grid(10006),
     GridItem(10007),
@@ -97,7 +97,7 @@ private Pattern(int value, Class type) {
     public static Pattern fromType(Class type) {
         return FROM_CLASS.get(type.getSimpleName());
     }
-    
+
     public static Pattern fromName(String name) {
         return FROM_NAME.get(name.toLowerCase());
     }
diff --git a/karate-robot/src/main/java/com/intuit/karate/robot/win/ScrollAmount.java b/karate-robot/src/main/java/com/intuit/karate/robot/win/ScrollAmount.java
new file mode 100644
index 000000000..0e307852e
--- /dev/null
+++ b/karate-robot/src/main/java/com/intuit/karate/robot/win/ScrollAmount.java
@@ -0,0 +1,44 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2021 Intuit Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.intuit.karate.robot.win;
+
+/**
+ *
+ * @author pthomas3
+ */
+public enum ScrollAmount {
+
+    LargeDecrement(0),
+    SmallDecrement(1),
+    NoAmount(2),
+    LargeIncrement(3),
+    SmallIncrement(4);
+
+    public final int value;
+
+    private ScrollAmount(int value) {
+        this.value = value;
+    }
+
+}
diff --git a/karate-robot/src/main/java/com/intuit/karate/robot/win/WinElement.java b/karate-robot/src/main/java/com/intuit/karate/robot/win/WinElement.java
index fcd66c7e1..5ee37fbe0 100644
--- a/karate-robot/src/main/java/com/intuit/karate/robot/win/WinElement.java
+++ b/karate-robot/src/main/java/com/intuit/karate/robot/win/WinElement.java
@@ -23,6 +23,7 @@
  */
 package com.intuit.karate.robot.win;
 
+import com.intuit.karate.Logger;
 import com.intuit.karate.robot.Element;
 import com.intuit.karate.robot.Location;
 import com.intuit.karate.robot.Region;
@@ -40,10 +41,12 @@ public class WinElement implements Element {
 
     protected final IUIAutomationElement e;
     private final WinRobot robot;
+    private final Logger logger;
 
     public WinElement(WinRobot robot, IUIAutomationElement e) {
         this.robot = robot;
         this.e = e;
+        this.logger = robot.getLogger();
     }
 
     @Override
@@ -132,6 +135,11 @@ private boolean isInvokePatternAvailable() {
         return variant.booleanValue();
     }
 
+    private boolean isScrollPatternAvailable() {
+        Variant.VARIANT variant = e.getCurrentPropertyValue(Property.IsScrollPatternAvailable);
+        return variant.booleanValue();
+    }
+
     @Override
     public String getValue() {
         if (isValuePatternAvailable()) {
@@ -248,6 +256,46 @@ public Element select() {
         return this;
     }
 
+    public Element scrollDown() {
+        return scrollDown(false);
+    }
+
+    public Element scrollUp() {
+        return scrollUp(false);
+    }
+
+    public Element scrollDown(boolean large) {
+        if (isScrollPatternAvailable()) {
+            IUIAutomationScrollPattern pattern = e.getCurrentPattern(IUIAutomationScrollPattern.class);
+            ScrollAmount sa = large ? ScrollAmount.LargeIncrement : ScrollAmount.SmallIncrement;
+            pattern.scroll(sa);
+        } else {
+            logger.warn("scroll pattern not available on: {}", getName());
+        }
+        return this;
+    }
+
+    public Element scrollUp(boolean large) {
+        if (isScrollPatternAvailable()) {
+            IUIAutomationScrollPattern pattern = e.getCurrentPattern(IUIAutomationScrollPattern.class);
+            ScrollAmount sa = large ? ScrollAmount.LargeDecrement : ScrollAmount.SmallDecrement;
+            pattern.scroll(sa);
+        } else {
+            logger.warn("scroll pattern not available on: {}", getName());
+        }
+        return this;
+    }
+
+    public Element scroll(double horizontalPercent, double verticalPercent) {
+        if (isScrollPatternAvailable()) {
+            IUIAutomationScrollPattern pattern = e.getCurrentPattern(IUIAutomationScrollPattern.class);
+            pattern.setScrollPercent(horizontalPercent, verticalPercent);
+        } else {
+            logger.warn("scroll pattern not available on: {}", getName());
+        }
+        return this;
+    }
+
     public Object as(String patternName) {
         Pattern pattern = Pattern.fromName(patternName);
         if (pattern == null) {