From 55039abff9f271e93a0ea4f75dc2eb0354924f5d Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Mon, 16 Oct 2017 10:43:52 +0200 Subject: [PATCH] Refactor setting of touch actions options --- .../io/appium/java_client/TouchAction.java | 306 +++++++++++++----- .../android/AndroidTouchAction.java | 29 ++ .../java_client/ios/IOSTouchAction.java | 39 ++- .../ios/touch/DoubleTapOptions.java | 26 ++ .../java_client/touch/ActionOptions.java | 40 +++ .../java_client/touch/LongPressOptions.java | 51 +++ .../java_client/touch/MoveToOptions.java | 20 ++ .../touch/OptionsWithAbsolutePositioning.java | 107 ++++++ .../touch/OptionsWithRelativePositioning.java | 79 +++++ .../java_client/touch/PressOptions.java | 20 ++ .../appium/java_client/touch/TapOptions.java | 47 +++ .../appium/java_client/touch/WaitOptions.java | 56 ++++ .../AndroidAbilityToUseSupplierTest.java | 28 +- .../java_client/android/AndroidTouchTest.java | 95 ++++-- 14 files changed, 820 insertions(+), 123 deletions(-) create mode 100644 src/main/java/io/appium/java_client/android/AndroidTouchAction.java create mode 100644 src/main/java/io/appium/java_client/ios/touch/DoubleTapOptions.java create mode 100644 src/main/java/io/appium/java_client/touch/ActionOptions.java create mode 100644 src/main/java/io/appium/java_client/touch/LongPressOptions.java create mode 100644 src/main/java/io/appium/java_client/touch/MoveToOptions.java create mode 100644 src/main/java/io/appium/java_client/touch/OptionsWithAbsolutePositioning.java create mode 100644 src/main/java/io/appium/java_client/touch/OptionsWithRelativePositioning.java create mode 100644 src/main/java/io/appium/java_client/touch/PressOptions.java create mode 100644 src/main/java/io/appium/java_client/touch/TapOptions.java create mode 100644 src/main/java/io/appium/java_client/touch/WaitOptions.java diff --git a/src/main/java/io/appium/java_client/TouchAction.java b/src/main/java/io/appium/java_client/TouchAction.java index ca30fde11..968d776b3 100644 --- a/src/main/java/io/appium/java_client/TouchAction.java +++ b/src/main/java/io/appium/java_client/TouchAction.java @@ -19,11 +19,18 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.touch.ActionOptions; +import io.appium.java_client.touch.LongPressOptions; +import io.appium.java_client.touch.MoveToOptions; +import io.appium.java_client.touch.PressOptions; +import io.appium.java_client.touch.TapOptions; +import io.appium.java_client.touch.WaitOptions; import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.HasIdentity; import java.time.Duration; +import static com.google.common.base.Preconditions.checkNotNull; + /** * Used for Webdriver 3 touch actions * See the Webriver 3 spec @@ -34,7 +41,7 @@ * Calling perform() sends the action command to the Mobile Driver. Otherwise, * more and more actions can be chained. */ -public class TouchAction implements PerformsActions { +public class TouchAction> implements PerformsActions { protected ImmutableList.Builder parameterBuilder; private PerformsTouchActions performsTouchActions; @@ -44,16 +51,33 @@ public TouchAction(PerformsTouchActions performsTouchActions) { parameterBuilder = ImmutableList.builder(); } + /** + * Press on an element. + * + * @param pressOptions see {@link PressOptions}. + * @return this TouchAction, for chaining. + */ + public T press(ActionOptions pressOptions) { + parameterBuilder.add(new ActionParameter("press", pressOptions)); + //noinspection unchecked + return (T) this; + } + /** * Press on the center of an element. * * @param el element to press on. * @return this TouchAction, for chaining. + * @deprecated use {@link #press(ActionOptions)} instead */ - public TouchAction press(WebElement el) { - ActionParameter action = new ActionParameter("press", (HasIdentity) el); + @Deprecated + public T press(WebElement el) { + ActionParameter action = new ActionParameter("press", + new PressOptions() + .withElement(el)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -62,13 +86,16 @@ public TouchAction press(WebElement el) { * @param x x coordinate. * @param y y coordinate. * @return this TouchAction, for chaining. + * @deprecated use {@link #press(ActionOptions)} instead */ - public TouchAction press(int x, int y) { - ActionParameter action = new ActionParameter("press"); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T press(int x, int y) { + ActionParameter action = new ActionParameter("press", + new PressOptions() + .withAbsoluteOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -78,13 +105,17 @@ public TouchAction press(int x, int y) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated use {@link #press(ActionOptions)} instead */ - public TouchAction press(WebElement el, int x, int y) { - ActionParameter action = new ActionParameter("press", (HasIdentity) el); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T press(WebElement el, int x, int y) { + ActionParameter action = new ActionParameter("press", + new PressOptions() + .withElement(el) + .withRelativeOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -92,10 +123,24 @@ public TouchAction press(WebElement el, int x, int y) { * * @return this TouchAction, for chaining. */ - public TouchAction release() { + public T release() { ActionParameter action = new ActionParameter("release"); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; + } + + /** + * Move current touch to center of an element. + * + * @param moveToOptions see {@link MoveToOptions}. + * @return this TouchAction, for chaining. + */ + public T moveTo(ActionOptions moveToOptions) { + ActionParameter action = new ActionParameter("moveTo", moveToOptions); + parameterBuilder.add(action); + //noinspection unchecked + return (T) this; } /** @@ -103,11 +148,16 @@ public TouchAction release() { * * @param el element to move to. * @return this TouchAction, for chaining. + * @deprecated {@link #moveTo(ActionOptions)} instead */ - public TouchAction moveTo(WebElement el) { - ActionParameter action = new ActionParameter("moveTo", (HasIdentity) el); + @Deprecated + public T moveTo(WebElement el) { + ActionParameter action = new ActionParameter("moveTo", + new MoveToOptions() + .withElement(el)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -118,13 +168,16 @@ public TouchAction moveTo(WebElement el) { * @param x change in x coordinate to move through. * @param y change in y coordinate to move through. * @return this TouchAction, for chaining. + * @deprecated {@link #moveTo(ActionOptions)} instead */ - public TouchAction moveTo(int x, int y) { - ActionParameter action = new ActionParameter("moveTo"); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T moveTo(int x, int y) { + ActionParameter action = new ActionParameter("moveTo", + new MoveToOptions() + .withRelativeOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -134,11 +187,27 @@ public TouchAction moveTo(int x, int y) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated {@link #moveTo(ActionOptions)} instead */ - public TouchAction moveTo(WebElement el, int x, int y) { - ActionParameter action = new ActionParameter("moveTo", (HasIdentity) el); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T moveTo(WebElement el, int x, int y) { + ActionParameter action = new ActionParameter("moveTo", + new MoveToOptions() + .withElement(el) + .withRelativeOffset(x, y)); + parameterBuilder.add(action); + //noinspection unchecked + return (T) this; + } + + /** + * Tap the center of an element. + * + * @param tapOptions see {@link TapOptions}. + * @return this TouchAction, for chaining. + */ + public TouchAction tap(ActionOptions tapOptions) { + ActionParameter action = new ActionParameter("tap", tapOptions); parameterBuilder.add(action); return this; } @@ -148,11 +217,16 @@ public TouchAction moveTo(WebElement el, int x, int y) { * * @param el element to tap. * @return this TouchAction, for chaining. + * @deprecated use {@link #tap(ActionOptions)} instead. */ - public TouchAction tap(WebElement el) { - ActionParameter action = new ActionParameter("tap", (HasIdentity) el); + @Deprecated + public T tap(WebElement el) { + ActionParameter action = new ActionParameter("tap", + new TapOptions() + .withElement(el)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -161,13 +235,16 @@ public TouchAction tap(WebElement el) { * @param x x coordinate. * @param y y coordinate. * @return this TouchAction, for chaining. + * @deprecated use {@link #tap(ActionOptions)} instead. */ - public TouchAction tap(int x, int y) { - ActionParameter action = new ActionParameter("tap"); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T tap(int x, int y) { + ActionParameter action = new ActionParameter("tap", + new TapOptions() + .withAbsoluteOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -177,13 +254,17 @@ public TouchAction tap(int x, int y) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated use {@link #tap(ActionOptions)} instead. */ - public TouchAction tap(WebElement el, int x, int y) { - ActionParameter action = new ActionParameter("tap", (HasIdentity) el); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T tap(WebElement el, int x, int y) { + ActionParameter action = new ActionParameter("tap", + new TapOptions() + .withElement(el) + .withRelativeOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -191,10 +272,24 @@ public TouchAction tap(WebElement el, int x, int y) { * * @return this TouchAction, for chaining. */ - public TouchAction waitAction() { + public T waitAction() { ActionParameter action = new ActionParameter("wait"); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; + } + + /** + * Waits for specified amount of time to pass before continue to next touch action. + * + * @param waitOptions see {@link WaitOptions}. + * @return this TouchAction, for chaining. + */ + public T waitAction(ActionOptions waitOptions) { + ActionParameter action = new ActionParameter("wait", waitOptions); + parameterBuilder.add(action); + //noinspection unchecked + return (T) this; } /** @@ -202,12 +297,29 @@ public TouchAction waitAction() { * * @param duration of the wait action. Minimum time reolution unit is one millisecond. * @return this TouchAction, for chaining. + * @deprecated use {@link #waitAction(ActionOptions)} instead. */ - public TouchAction waitAction(Duration duration) { - ActionParameter action = new ActionParameter("wait"); - action.addParameter("ms", duration.toMillis()); + @Deprecated + public T waitAction(Duration duration) { + ActionParameter action = new ActionParameter("wait", + new WaitOptions() + .withDuration(duration)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; + } + + /** + * Press and hold the at the center of an element until the context menu event has fired. + * + * @param longPressOptions see {@link LongPressOptions}. + * @return this TouchAction, for chaining. + */ + public T longPress(ActionOptions longPressOptions) { + ActionParameter action = new ActionParameter("longPress", longPressOptions); + parameterBuilder.add(action); + //noinspection unchecked + return (T) this; } /** @@ -215,11 +327,16 @@ public TouchAction waitAction(Duration duration) { * * @param el element to long-press. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(ActionOptions)} instead */ - public TouchAction longPress(WebElement el) { - ActionParameter action = new ActionParameter("longPress", (HasIdentity) el); + @Deprecated + public T longPress(WebElement el) { + ActionParameter action = new ActionParameter("longPress", + new LongPressOptions() + .withElement(el)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -228,12 +345,17 @@ public TouchAction longPress(WebElement el) { * @param el element to long-press. * @param duration of the long-press. Minimum time resolution unit is one millisecond. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(ActionOptions)} instead */ - public TouchAction longPress(WebElement el, Duration duration) { - ActionParameter action = new ActionParameter("longPress", (HasIdentity) el); - action.addParameter("duration", duration.toMillis()); + @Deprecated + public T longPress(WebElement el, Duration duration) { + ActionParameter action = new ActionParameter("longPress", + new LongPressOptions() + .withElement(el) + .withDuration(duration)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -243,13 +365,16 @@ public TouchAction longPress(WebElement el, Duration duration) { * @param x x coordinate. * @param y y coordinate. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(ActionOptions)} instead */ - public TouchAction longPress(int x, int y) { - ActionParameter action = new ActionParameter("longPress"); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T longPress(int x, int y) { + ActionParameter action = new ActionParameter("longPress", + new LongPressOptions() + .withAbsoluteOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -260,17 +385,19 @@ public TouchAction longPress(int x, int y) { * @param y y coordinate. * @param duration of the long-press. Minimum time resolution unit is one millisecond. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(ActionOptions)} instead */ - public TouchAction longPress(int x, int y, Duration duration) { - ActionParameter action = new ActionParameter("longPress"); - action.addParameter("x", x); - action.addParameter("y", y); - action.addParameter("duration", duration.toMillis()); + @Deprecated + public T longPress(int x, int y, Duration duration) { + ActionParameter action = new ActionParameter("longPress", + new LongPressOptions() + .withAbsoluteOffset(x, y) + .withDuration(duration)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } - /** * Press and hold the at an elements upper-left corner, offset by the given amount, * until the contextmenu event has fired. @@ -279,13 +406,17 @@ public TouchAction longPress(int x, int y, Duration duration) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(ActionOptions)} instead */ - public TouchAction longPress(WebElement el, int x, int y) { - ActionParameter action = new ActionParameter("longPress", (HasIdentity) el); - action.addParameter("x", x); - action.addParameter("y", y); + @Deprecated + public T longPress(WebElement el, int x, int y) { + ActionParameter action = new ActionParameter("longPress", + new LongPressOptions() + .withElement(el) + .withRelativeOffset(x, y)); parameterBuilder.add(action); - return this; + //noinspection unchecked + return (T) this; } /** @@ -297,12 +428,15 @@ public TouchAction longPress(WebElement el, int x, int y) { * @param y y offset. * @param duration of the long-press. Minimum time resolution unit is one millisecond. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(ActionOptions)} instead */ + @Deprecated public TouchAction longPress(WebElement el, int x, int y, Duration duration) { - ActionParameter action = new ActionParameter("longPress", (HasIdentity) el); - action.addParameter("x", x); - action.addParameter("y", y); - action.addParameter("duration", duration.toMillis()); + ActionParameter action = new ActionParameter("longPress", + new LongPressOptions() + .withElement(el) + .withRelativeOffset(x, y) + .withDuration(duration)); parameterBuilder.add(action); return this; } @@ -321,9 +455,10 @@ public void cancel() { * * @return this TouchAction, for possible segmented-touches. */ - public TouchAction perform() { + public T perform() { performsTouchActions.performTouchAction(this); - return this; + //noinspection unchecked + return (T) this; } /** @@ -345,9 +480,10 @@ protected ImmutableMap> getParameters() { * * @return this TouchAction, for possible segmented-touches. */ - protected TouchAction clearParameters() { + protected T clearParameters() { parameterBuilder = ImmutableList.builder(); - return this; + //noinspection unchecked + return (T) this; } /** @@ -362,10 +498,12 @@ public ActionParameter(String actionName) { optionsBuilder = ImmutableMap.builder(); } - public ActionParameter(String actionName, HasIdentity el) { + public ActionParameter(String actionName, ActionOptions opts) { + checkNotNull(opts); this.actionName = actionName; optionsBuilder = ImmutableMap.builder(); - addParameter("element", el.getId()); + //noinspection unchecked + optionsBuilder.putAll(opts.build()); } public ImmutableMap getParameterMap() { @@ -373,9 +511,5 @@ public ImmutableMap getParameterMap() { builder.put("action", actionName).put("options", optionsBuilder.build()); return builder.build(); } - - public void addParameter(String name, Object value) { - optionsBuilder.put(name, value); - } } } diff --git a/src/main/java/io/appium/java_client/android/AndroidTouchAction.java b/src/main/java/io/appium/java_client/android/AndroidTouchAction.java new file mode 100644 index 000000000..e1af310df --- /dev/null +++ b/src/main/java/io/appium/java_client/android/AndroidTouchAction.java @@ -0,0 +1,29 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.android; + +import io.appium.java_client.PerformsTouchActions; +import io.appium.java_client.TouchAction; + + +public class AndroidTouchAction extends TouchAction { + + public AndroidTouchAction(PerformsTouchActions performsTouchActions) { + super(performsTouchActions); + } + +} diff --git a/src/main/java/io/appium/java_client/ios/IOSTouchAction.java b/src/main/java/io/appium/java_client/ios/IOSTouchAction.java index 104fc8f8b..2b0ed79af 100644 --- a/src/main/java/io/appium/java_client/ios/IOSTouchAction.java +++ b/src/main/java/io/appium/java_client/ios/IOSTouchAction.java @@ -1,12 +1,28 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.appium.java_client.ios; import io.appium.java_client.PerformsTouchActions; import io.appium.java_client.TouchAction; +import io.appium.java_client.ios.touch.DoubleTapOptions; import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.HasIdentity; -public class IOSTouchAction extends TouchAction { +public class IOSTouchAction extends TouchAction { public IOSTouchAction(PerformsTouchActions performsTouchActions) { super(performsTouchActions); @@ -18,12 +34,15 @@ public IOSTouchAction(PerformsTouchActions performsTouchActions) { * @param el element to tap. * @param x x offset. * @param y y offset. - * @return this TouchAction, for chaining. + * @return this IOSTouchAction, for chaining. + * @deprecated use {@link #tap(ActionOptions)} with count=2 instead. */ + @Deprecated public IOSTouchAction doubleTap(WebElement el, int x, int y) { - ActionParameter action = new ActionParameter("doubleTap", (HasIdentity) el); - action.addParameter("x", x); - action.addParameter("y", y); + ActionParameter action = new ActionParameter("doubleTap", + new DoubleTapOptions() + .withElement(el) + .withRelativeOffset(x, y)); parameterBuilder.add(action); return this; } @@ -32,10 +51,14 @@ public IOSTouchAction doubleTap(WebElement el, int x, int y) { * Double taps an element, offset from upper left corner. * * @param el element to tap. - * @return this TouchAction, for chaining. + * @return this IOSTouchAction, for chaining. + * @deprecated use {@link #tap(ActionOptions)} with count=2 instead. */ + @Deprecated public IOSTouchAction doubleTap(WebElement el) { - ActionParameter action = new ActionParameter("doubleTap", (HasIdentity) el); + ActionParameter action = new ActionParameter("doubleTap", + new DoubleTapOptions() + .withElement(el)); parameterBuilder.add(action); return this; } diff --git a/src/main/java/io/appium/java_client/ios/touch/DoubleTapOptions.java b/src/main/java/io/appium/java_client/ios/touch/DoubleTapOptions.java new file mode 100644 index 000000000..3149d287f --- /dev/null +++ b/src/main/java/io/appium/java_client/ios/touch/DoubleTapOptions.java @@ -0,0 +1,26 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.ios.touch; + +import io.appium.java_client.touch.OptionsWithAbsolutePositioning; + +/** + * @deprecated this class will be removed + */ +@Deprecated +public class DoubleTapOptions extends OptionsWithAbsolutePositioning { +} diff --git a/src/main/java/io/appium/java_client/touch/ActionOptions.java b/src/main/java/io/appium/java_client/touch/ActionOptions.java new file mode 100644 index 000000000..2673142e4 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/ActionOptions.java @@ -0,0 +1,40 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +import java.util.HashMap; +import java.util.Map; + +public abstract class ActionOptions> { + /** + * This method is automatically called before building + * options map to verify the consistency of the instance. + * + * @throws IllegalArgumentException if there are problems with this options map. + */ + protected abstract void verify(); + + /** + * Creates a map based on the provided options. + * + * @return options mapping. + */ + public Map build() { + verify(); + return new HashMap<>(); + } +} diff --git a/src/main/java/io/appium/java_client/touch/LongPressOptions.java b/src/main/java/io/appium/java_client/touch/LongPressOptions.java new file mode 100644 index 000000000..42d63aabf --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/LongPressOptions.java @@ -0,0 +1,51 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +import java.time.Duration; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class LongPressOptions extends OptionsWithAbsolutePositioning { + protected Duration duration = null; + + /** + * Set the long press duration. + * + * @param duration the duration value to set. + * Time resolution unit is 1 ms. + * @return this instance for chaining. + */ + public LongPressOptions withDuration(Duration duration) { + checkNotNull(duration); + checkArgument(duration.toMillis() >= 0, + "Duration value should be greater or equal to zero"); + this.duration = duration; + return this; + } + + @Override + public Map build() { + final Map result = super.build(); + if (duration != null) { + result.put("duration", this.duration.toMillis()); + } + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/MoveToOptions.java b/src/main/java/io/appium/java_client/touch/MoveToOptions.java new file mode 100644 index 000000000..b081ef973 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/MoveToOptions.java @@ -0,0 +1,20 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +public class MoveToOptions extends OptionsWithRelativePositioning { +} diff --git a/src/main/java/io/appium/java_client/touch/OptionsWithAbsolutePositioning.java b/src/main/java/io/appium/java_client/touch/OptionsWithAbsolutePositioning.java new file mode 100644 index 000000000..989059f50 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/OptionsWithAbsolutePositioning.java @@ -0,0 +1,107 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class OptionsWithAbsolutePositioning> + extends ActionOptions { + private String elementId = null; + private Point absoluteOffset = null; + private Point relativeOffset = null; + + /** + * Set the destination element for the corresponding action. + * + * @param element the destination element. + * @return this instance for chaining. + */ + public T withElement(WebElement element) { + checkNotNull(element); + this.elementId = ((HasIdentity) element).getId(); + //noinspection unchecked + return (T) this; + } + + /** + * Set the absolute offset for the corresponding action. + * + * @param xOffset the absolute distance from the left screen corner (the element must not be set). + * @param yOffset the absolute distance from the top screen corner. + * @return this instance for chaining. + */ + public T withAbsoluteOffset(int xOffset, int yOffset) { + this.absoluteOffset = new Point(xOffset, yOffset); + //noinspection unchecked + return (T) this; + } + + /** + * Set the relative offset for the corresponding action. + * + * @param xOffset the relative distance from the left element corner (the element must be set). + * @param yOffset the relative distance from the top element corner (the element must be set). + * @return this instance for chaining. + */ + public T withRelativeOffset(int xOffset, int yOffset) { + this.relativeOffset = new Point(xOffset, yOffset); + //noinspection unchecked + return (T) this; + } + + @Override + protected void verify() { + if (elementId == null) { + if (absoluteOffset != null) { + throw new IllegalArgumentException("Absolute offset must not be defined if 'element' option is set"); + } + if (relativeOffset == null) { + throw new IllegalArgumentException("Relative offset must be defined if 'element' option is set"); + } + } else { + if (absoluteOffset == null) { + throw new IllegalArgumentException("Absolute offset must be defined if 'element' option not set"); + } + if (relativeOffset != null) { + throw new IllegalArgumentException("Relative offset must not be defined if 'element' option not set"); + } + } + } + + @Override + public Map build() { + final Map result = super.build(); + if (absoluteOffset != null) { + result.put("x", absoluteOffset.x); + result.put("y", absoluteOffset.y); + } + if (relativeOffset != null) { + result.put("x", relativeOffset.x); + result.put("y", relativeOffset.y); + } + if (elementId != null) { + result.put("element", elementId); + } + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/OptionsWithRelativePositioning.java b/src/main/java/io/appium/java_client/touch/OptionsWithRelativePositioning.java new file mode 100644 index 000000000..786101b73 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/OptionsWithRelativePositioning.java @@ -0,0 +1,79 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; + +public abstract class OptionsWithRelativePositioning> + extends ActionOptions { + private String elementId = null; + private Point relativeOffset = null; + + /** + * Set the destination element for the corresponding action. + * + * @param element the destination element. + * @return this instance for chaining. + */ + public T withElement(WebElement element) { + checkNotNull(element); + this.elementId = ((HasIdentity) element).getId(); + //noinspection unchecked + return (T) this; + } + + /** + * Set the relative offset for the corresponding action. + * + * @param xOffset the relative distance from the left element corner + * (if set) or from the left corner of the preceding chain action. + * @param yOffset the relative distance from the top element corner + * (if set) or from the top corner of the preceding chain action. + * @return this instance for chaining. + */ + public T withRelativeOffset(int xOffset, int yOffset) { + this.relativeOffset = new Point(xOffset, yOffset); + //noinspection unchecked + return (T) this; + } + + @Override + protected void verify() { + if (elementId == null && relativeOffset == null) { + throw new IllegalArgumentException("Either element or relative offset should be defined"); + } + } + + @Override + public Map build() { + final Map result = super.build(); + if (relativeOffset != null) { + result.put("x", relativeOffset.x); + result.put("y", relativeOffset.y); + } + if (elementId != null) { + result.put("element", elementId); + } + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/PressOptions.java b/src/main/java/io/appium/java_client/touch/PressOptions.java new file mode 100644 index 000000000..1d51fe720 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/PressOptions.java @@ -0,0 +1,20 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +public class PressOptions extends OptionsWithAbsolutePositioning { +} diff --git a/src/main/java/io/appium/java_client/touch/TapOptions.java b/src/main/java/io/appium/java_client/touch/TapOptions.java new file mode 100644 index 000000000..35fe5cb78 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/TapOptions.java @@ -0,0 +1,47 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; + +public class TapOptions extends OptionsWithAbsolutePositioning { + private Integer tapsCount = null; + + /** + * Set the count of taps to perform. + * + * @param tapsCount the taps count to perform. + * The value should be greater than zero. + * @return this instance for chaining. + */ + public TapOptions withTapsCount(int tapsCount) { + checkArgument(tapsCount > 0, "Taps count should be greater than zero"); + this.tapsCount = tapsCount; + return this; + } + + @Override + public Map build() { + final Map result = super.build(); + if (tapsCount != null) { + result.put("count", this.tapsCount); + } + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/WaitOptions.java b/src/main/java/io/appium/java_client/touch/WaitOptions.java new file mode 100644 index 000000000..784329e69 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/WaitOptions.java @@ -0,0 +1,56 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.appium.java_client.touch; + +import java.time.Duration; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +public class WaitOptions extends ActionOptions { + protected Duration duration = Duration.ofMillis(0); + + /** + * Set the wait duration. + * + * @param duration the duration value to set. + * Time resolution unit is 1 ms. + * @return this instance for chaining. + */ + public WaitOptions withDuration(Duration duration) { + checkNotNull(duration); + checkArgument(duration.toMillis() >= 0, + "Duration value should be greater or equal to zero"); + this.duration = duration; + return this; + } + + @Override + protected void verify() { + // Wait options have nothing to verify + } + + @Override + public Map build() { + final Map result = super.build(); + if (duration != null) { + result.put("ms", this.duration.toMillis()); + } + return result; + } +} diff --git a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java index 607f84a7f..b4d12cf84 100644 --- a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java @@ -3,8 +3,10 @@ import static org.junit.Assert.assertNotEquals; import io.appium.java_client.MobileElement; -import io.appium.java_client.TouchAction; import io.appium.java_client.functions.ActionSupplier; +import io.appium.java_client.touch.MoveToOptions; +import io.appium.java_client.touch.PressOptions; +import io.appium.java_client.touch.WaitOptions; import org.junit.Test; import org.openqa.selenium.Point; @@ -13,7 +15,7 @@ public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { - private final ActionSupplier horizontalSwipe = () -> { + private final ActionSupplier horizontalSwipe = () -> { driver.findElementById("io.appium.android.apis:id/gallery"); AndroidElement gallery = driver.findElementById("io.appium.android.apis:id/gallery"); @@ -22,13 +24,25 @@ public class AndroidAbilityToUseSupplierTest extends BaseAndroidTest { Point location = gallery.getLocation(); Point center = gallery.getCenter(); - return new TouchAction(driver).press(images.get(2), -10, center.y - location.y) - .waitAction(Duration.ofSeconds(2)).moveTo(gallery, 10, center.y - location.y).release(); + return new AndroidTouchAction(driver) + .press(new PressOptions() + .withElement(images.get(2)) + .withRelativeOffset( -10, center.y - location.y)) + .waitAction(new WaitOptions().withDuration(Duration.ofSeconds(2))) + .moveTo(new MoveToOptions() + .withElement(gallery) + .withRelativeOffset( 10, center.y - location.y)) + .release(); }; - private final ActionSupplier verticalSwiping = () -> - new TouchAction(driver).press(driver.findElementByAccessibilityId("Gallery")) - .waitAction(Duration.ofSeconds(2)).moveTo(driver.findElementByAccessibilityId("Auto Complete")) + private final ActionSupplier verticalSwiping = () -> + new AndroidTouchAction(driver) + .press(new PressOptions() + .withElement(driver.findElementByAccessibilityId("Gallery"))) + .waitAction(new WaitOptions() + .withDuration(Duration.ofSeconds(2))) + .moveTo(new MoveToOptions() + .withElement(driver.findElementByAccessibilityId("Auto Complete"))) .release(); @Test public void horizontalSwipingWithSupplier() throws Exception { diff --git a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java index 3a4cc3651..21a15c4fd 100644 --- a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java @@ -6,6 +6,11 @@ import io.appium.java_client.MobileElement; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; +import io.appium.java_client.touch.LongPressOptions; +import io.appium.java_client.touch.MoveToOptions; +import io.appium.java_client.touch.PressOptions; +import io.appium.java_client.touch.TapOptions; +import io.appium.java_client.touch.WaitOptions; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.By; @@ -31,8 +36,12 @@ public void setUp() throws Exception { WebElement dragText = driver.findElement(By.id("io.appium.android.apis:id/drag_text")); assertEquals("Drag text not empty", "", dragText.getText()); - TouchAction dragNDrop = - new TouchAction(driver).longPress(dragDot1).moveTo(dragDot3).release(); + TouchAction dragNDrop = new TouchAction(driver) + .longPress(new LongPressOptions() + .withElement(dragDot1)) + .moveTo(new MoveToOptions() + .withElement(dragDot3)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -46,8 +55,13 @@ public void setUp() throws Exception { WebElement dragText = driver.findElement(By.id("io.appium.android.apis:id/drag_text")); assertEquals("Drag text not empty", "", dragText.getText()); - TouchAction dragNDrop = - new TouchAction(driver).longPress(dragDot1, Duration.ofSeconds(2)).moveTo(dragDot3).release(); + TouchAction dragNDrop = new TouchAction(driver) + .longPress(new LongPressOptions() + .withElement(dragDot1) + .withDuration(Duration.ofSeconds(2))) + .moveTo(new MoveToOptions() + .withElement(dragDot3)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -64,8 +78,12 @@ public void setUp() throws Exception { Point center1 = dragDot1.getCenter(); Point center2 = dragDot3.getCenter(); - TouchAction dragNDrop = - new TouchAction(driver).longPress(center1.x, center1.y).moveTo(center2.x, center2.y).release(); + TouchAction dragNDrop = new TouchAction(driver) + .longPress(new LongPressOptions() + .withAbsoluteOffset(center1.x, center1.y)) + .moveTo(new MoveToOptions() + .withRelativeOffset(center2.x, center2.y)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -82,9 +100,13 @@ public void setUp() throws Exception { Point center1 = dragDot1.getCenter(); Point center2 = dragDot3.getCenter(); - TouchAction dragNDrop = - new TouchAction(driver).longPress(center1.x, center1.y, Duration.ofSeconds(2)) - .moveTo(center2.x, center2.y).release(); + TouchAction dragNDrop = new TouchAction(driver) + .longPress(new LongPressOptions() + .withAbsoluteOffset(center1.x, center1.y) + .withDuration(Duration.ofSeconds(2))) + .moveTo(new MoveToOptions() + .withRelativeOffset(center2.x, center2.y)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -94,8 +116,13 @@ public void setUp() throws Exception { driver.startActivity(activity); Point point = driver.findElementById("io.appium.android.apis:id/button_toggle").getLocation(); - new TouchAction(driver).press(point.x + 20, point.y + 30).waitAction(Duration.ofSeconds(1)) - .release().perform(); + new TouchAction(driver) + .press(new PressOptions() + .withAbsoluteOffset(point.x + 20, point.y + 30)) + .waitAction(new WaitOptions() + .withDuration(Duration.ofSeconds(1))) + .release() + .perform(); assertEquals("ON" ,driver .findElementById("io.appium.android.apis:id/button_toggle").getText()); } @@ -103,8 +130,13 @@ public void setUp() throws Exception { @Test public void pressByElementTest() throws Exception { Activity activity = new Activity("io.appium.android.apis", ".view.Buttons1"); driver.startActivity(activity); - new TouchAction(driver).press(driver.findElementById("io.appium.android.apis:id/button_toggle")) - .waitAction(Duration.ofSeconds(1)).release().perform(); + new TouchAction(driver) + .press(new PressOptions() + .withElement(driver.findElementById("io.appium.android.apis:id/button_toggle"))) + .waitAction(new WaitOptions() + .withDuration(Duration.ofSeconds(1))) + .release() + .perform(); assertEquals("ON" ,driver .findElementById("io.appium.android.apis:id/button_toggle").getText()); } @@ -116,8 +148,12 @@ public void setUp() throws Exception { driver.findElementById("io.appium.android.apis:id/chronometer"); TouchAction startStop = new TouchAction(driver) - .tap(driver.findElementById("io.appium.android.apis:id/start")).waitAction(Duration.ofSeconds(2)) - .tap(driver.findElementById("io.appium.android.apis:id/stop")); + .tap(new TapOptions() + .withElement(driver.findElementById("io.appium.android.apis:id/start"))) + .waitAction(new WaitOptions() + .withDuration(Duration.ofSeconds(2))) + .tap(new TapOptions() + .withElement(driver.findElementById("io.appium.android.apis:id/stop"))); startStop.perform(); @@ -136,8 +172,11 @@ public void setUp() throws Exception { Point center1 = driver.findElementById("io.appium.android.apis:id/start").getCenter(); TouchAction startStop = new TouchAction(driver) - .tap(center1.x, center1.y) - .tap(driver.findElementById("io.appium.android.apis:id/stop"), 5, 5); + .tap(new TapOptions() + .withAbsoluteOffset(center1.x, center1.y)) + .tap(new TapOptions() + .withElement(driver.findElementById("io.appium.android.apis:id/stop")) + .withRelativeOffset(5, 5)); startStop.perform(); String time = chronometer.getText(); @@ -157,8 +196,16 @@ public void setUp() throws Exception { Point location = gallery.getLocation(); Point center = gallery.getCenter(); - TouchAction swipe = new TouchAction(driver).press(images.get(2), -10, center.y - location.y) - .waitAction(Duration.ofSeconds(2)).moveTo(gallery, 10, center.y - location.y).release(); + TouchAction swipe = new TouchAction(driver) + .press(new PressOptions() + .withElement(images.get(2)) + .withRelativeOffset( -10, center.y - location.y)) + .waitAction(new WaitOptions() + .withDuration(Duration.ofSeconds(2))) + .moveTo(new MoveToOptions() + .withElement(gallery) + .withRelativeOffset( 10, center.y - location.y)) + .release(); swipe.perform(); assertNotEquals(originalImageCount, gallery .findElementsByClassName("android.widget.ImageView").size()); @@ -167,10 +214,14 @@ public void setUp() throws Exception { @Test public void multiTouchTest() throws Exception { Activity activity = new Activity("io.appium.android.apis", ".view.Buttons1"); driver.startActivity(activity); - TouchAction press = new TouchAction(driver); - press.press(driver.findElementById("io.appium.android.apis:id/button_toggle")).waitAction(Duration.ofSeconds(1)) + TouchAction press = new TouchAction(driver) + .press(new PressOptions() + .withElement(driver.findElementById("io.appium.android.apis:id/button_toggle"))) + .waitAction(new WaitOptions() + .withDuration(Duration.ofSeconds(1))) .release(); - new MultiTouchAction(driver).add(press) + new MultiTouchAction(driver) + .add(press) .perform(); assertEquals("ON" ,driver .findElementById("io.appium.android.apis:id/button_toggle").getText());