diff --git a/src/main/java/io/appium/java_client/TouchAction.java b/src/main/java/io/appium/java_client/TouchAction.java index ca30fde11..41b847877 100644 --- a/src/main/java/io/appium/java_client/TouchAction.java +++ b/src/main/java/io/appium/java_client/TouchAction.java @@ -16,11 +16,22 @@ package io.appium.java_client; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.collect.ImmutableList.builder; +import static io.appium.java_client.touch.LongPressOptions.longPressOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static io.appium.java_client.touch.offset.PointOption.point; + 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.TapOptions; +import io.appium.java_client.touch.WaitOptions; +import io.appium.java_client.touch.offset.ElementOption; +import io.appium.java_client.touch.offset.PointOption; import org.openqa.selenium.WebElement; -import org.openqa.selenium.internal.HasIdentity; import java.time.Duration; @@ -34,14 +45,26 @@ * 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; public TouchAction(PerformsTouchActions performsTouchActions) { this.performsTouchActions = performsTouchActions; - parameterBuilder = ImmutableList.builder(); + parameterBuilder = builder(); + } + + /** + * Press action on the screen. + * + * @param pressOptions see {@link PointOption} and {@link ElementOption}. + * @return this TouchAction, for chaining. + */ + public T press(PointOption pressOptions) { + parameterBuilder.add(new ActionParameter("press", pressOptions)); + //noinspection unchecked + return (T) this; } /** @@ -49,11 +72,11 @@ public TouchAction(PerformsTouchActions performsTouchActions) { * * @param el element to press on. * @return this TouchAction, for chaining. + * @deprecated use {@link #press(PointOption)} instead */ - public TouchAction press(WebElement el) { - ActionParameter action = new ActionParameter("press", (HasIdentity) el); - parameterBuilder.add(action); - return this; + @Deprecated + public T press(WebElement el) { + return press(element(el)); } /** @@ -62,13 +85,11 @@ public TouchAction press(WebElement el) { * @param x x coordinate. * @param y y coordinate. * @return this TouchAction, for chaining. + * @deprecated use {@link #press(PointOption)} instead */ - public TouchAction press(int x, int y) { - ActionParameter action = new ActionParameter("press"); - action.addParameter("x", x); - action.addParameter("y", y); - parameterBuilder.add(action); - return this; + @Deprecated + public T press(int x, int y) { + return press(point(x, y)); } /** @@ -78,13 +99,11 @@ public TouchAction press(int x, int y) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated use {@link #press(PointOption)} 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); - parameterBuilder.add(action); - return this; + @Deprecated + public T press(WebElement el, int x, int y) { + return press(element(el, x, y)); } /** @@ -92,10 +111,23 @@ 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; + } + + /** + * Moves current touch to a new position. + * + * @param moveToOptions see {@link PointOption} and {@link ElementOption} + * @return this TouchAction, for chaining. + */ + public T moveTo(PointOption moveToOptions) { + ActionParameter action = new ActionParameter("moveTo", moveToOptions); + parameterBuilder.add(action); + return (T) this; } /** @@ -103,11 +135,11 @@ public TouchAction release() { * * @param el element to move to. * @return this TouchAction, for chaining. + * @deprecated {@link #moveTo(PointOption)} instead */ - public TouchAction moveTo(WebElement el) { - ActionParameter action = new ActionParameter("moveTo", (HasIdentity) el); - parameterBuilder.add(action); - return this; + @Deprecated + public T moveTo(WebElement el) { + return moveTo(element(el)); } /** @@ -118,13 +150,11 @@ 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(PointOption)} instead */ - public TouchAction moveTo(int x, int y) { - ActionParameter action = new ActionParameter("moveTo"); - action.addParameter("x", x); - action.addParameter("y", y); - parameterBuilder.add(action); - return this; + @Deprecated + public T moveTo(int x, int y) { + return moveTo(point(x, y)); } /** @@ -134,13 +164,35 @@ public TouchAction moveTo(int x, int y) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated {@link #moveTo(PointOption)} 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) { + return moveTo(element(el, x, y)); + } + + /** + * Tap on an element. + * + * @param tapOptions see {@link TapOptions}. + * @return this TouchAction, for chaining. + */ + public T tap(TapOptions tapOptions) { + ActionParameter action = new ActionParameter("tap", tapOptions); parameterBuilder.add(action); - return this; + return (T) this; + } + + /** + * Tap on a position. + * + * @param tapOptions see {@link PointOption} and {@link ElementOption} + * @return this TouchAction, for chaining. + */ + public T tap(PointOption tapOptions) { + ActionParameter action = new ActionParameter("tap", tapOptions); + parameterBuilder.add(action); + return (T) this; } /** @@ -148,11 +200,11 @@ public TouchAction moveTo(WebElement el, int x, int y) { * * @param el element to tap. * @return this TouchAction, for chaining. + * @deprecated use {@link #tap(PointOption)} instead. */ - public TouchAction tap(WebElement el) { - ActionParameter action = new ActionParameter("tap", (HasIdentity) el); - parameterBuilder.add(action); - return this; + @Deprecated + public T tap(WebElement el) { + return tap(element(el)); } /** @@ -161,13 +213,11 @@ public TouchAction tap(WebElement el) { * @param x x coordinate. * @param y y coordinate. * @return this TouchAction, for chaining. + * @deprecated use {@link #tap(PointOption)} instead. */ - public TouchAction tap(int x, int y) { - ActionParameter action = new ActionParameter("tap"); - action.addParameter("x", x); - action.addParameter("y", y); - parameterBuilder.add(action); - return this; + @Deprecated + public T tap(int x, int y) { + return tap(point(x, y)); } /** @@ -177,13 +227,11 @@ public TouchAction tap(int x, int y) { * @param x x offset. * @param y y offset. * @return this TouchAction, for chaining. + * @deprecated use {@link #tap(PointOption)} 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); - parameterBuilder.add(action); - return this; + @Deprecated + public T tap(WebElement el, int x, int y) { + return tap(element(el, x, y)); } /** @@ -191,10 +239,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(WaitOptions waitOptions) { + ActionParameter action = new ActionParameter("wait", waitOptions); + parameterBuilder.add(action); + //noinspection unchecked + return (T) this; } /** @@ -202,12 +264,42 @@ 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(WaitOptions)} 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); + //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(LongPressOptions longPressOptions) { + ActionParameter action = new ActionParameter("longPress", longPressOptions); 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 PointOption} and {@link ElementOption}. + * @return this TouchAction, for chaining. + */ + public T longPress(PointOption longPressOptions) { + ActionParameter action = new ActionParameter("longPress", longPressOptions); + parameterBuilder.add(action); + //noinspection unchecked + return (T) this; } /** @@ -215,11 +307,11 @@ public TouchAction waitAction(Duration duration) { * * @param el element to long-press. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(PointOption)} instead */ - public TouchAction longPress(WebElement el) { - ActionParameter action = new ActionParameter("longPress", (HasIdentity) el); - parameterBuilder.add(action); - return this; + @Deprecated + public T longPress(WebElement el) { + return longPress(element(el)); } /** @@ -228,12 +320,12 @@ 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(LongPressOptions)} instead */ - public TouchAction longPress(WebElement el, Duration duration) { - ActionParameter action = new ActionParameter("longPress", (HasIdentity) el); - action.addParameter("duration", duration.toMillis()); - parameterBuilder.add(action); - return this; + @Deprecated + public T longPress(WebElement el, Duration duration) { + return longPress(longPressOptions() + .withDuration(duration).withElement(element(el))); } /** @@ -243,13 +335,11 @@ public TouchAction longPress(WebElement el, Duration duration) { * @param x x coordinate. * @param y y coordinate. * @return this TouchAction, for chaining. + * @deprecated use {@link #longPress(PointOption)} instead */ - public TouchAction longPress(int x, int y) { - ActionParameter action = new ActionParameter("longPress"); - action.addParameter("x", x); - action.addParameter("y", y); - parameterBuilder.add(action); - return this; + @Deprecated + public T longPress(int x, int y) { + return longPress(point(x, y)); } /** @@ -260,17 +350,14 @@ 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(LongPressOptions)} 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()); - parameterBuilder.add(action); - return this; + @Deprecated + public T longPress(int x, int y, Duration duration) { + return longPress(longPressOptions() + .withDuration(duration).withPosition(point(x, y))); } - /** * Press and hold the at an elements upper-left corner, offset by the given amount, * until the contextmenu event has fired. @@ -279,13 +366,11 @@ 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(PointOption)} 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); - parameterBuilder.add(action); - return this; + @Deprecated + public T longPress(WebElement el, int x, int y) { + return longPress(element(el, x, y)); } /** @@ -297,14 +382,12 @@ 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(LongPressOptions)} instead */ - 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()); - parameterBuilder.add(action); - return this; + @Deprecated + public T longPress(WebElement el, int x, int y, Duration duration) { + return longPress(longPressOptions() + .withElement(element(el, x, y)).withDuration(duration)); } /** @@ -321,9 +404,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; } /** @@ -333,7 +417,7 @@ public TouchAction perform() { */ protected ImmutableMap> getParameters() { - ImmutableList.Builder parameters = ImmutableList.builder(); + ImmutableList.Builder parameters = builder(); ImmutableList actionList = parameterBuilder.build(); actionList.forEach(action -> parameters.add(action.getParameterMap())); @@ -345,9 +429,10 @@ protected ImmutableMap> getParameters() { * * @return this TouchAction, for possible segmented-touches. */ - protected TouchAction clearParameters() { - parameterBuilder = ImmutableList.builder(); - return this; + protected T clearParameters() { + parameterBuilder = builder(); + //noinspection unchecked + return (T) this; } /** @@ -362,10 +447,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 +460,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..aa9736892 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,30 @@ +/* + * 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 static io.appium.java_client.touch.offset.ElementOption.element; + import io.appium.java_client.PerformsTouchActions; import io.appium.java_client.TouchAction; +import io.appium.java_client.touch.offset.ElementOption; +import io.appium.java_client.touch.offset.PointOption; 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,24 +36,35 @@ 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 #doubleTap(PointOption)} 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); - parameterBuilder.add(action); - return this; + return doubleTap(element(el, x, 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 #doubleTap(PointOption)} with count=2 instead. */ + @Deprecated public IOSTouchAction doubleTap(WebElement el) { - ActionParameter action = new ActionParameter("doubleTap", (HasIdentity) el); + return doubleTap(element(el)); + } + + /** + * Double taps using coordinates. + * + * @param doubleTapOption see {@link PointOption} and {@link ElementOption}.. + * @return self-reference + */ + public IOSTouchAction doubleTap(PointOption doubleTapOption) { + ActionParameter action = new ActionParameter("doubleTap", + doubleTapOption); parameterBuilder.add(action); return this; } 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..198f476b5 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/LongPressOptions.java @@ -0,0 +1,62 @@ +/* + * 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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + +import io.appium.java_client.touch.offset.AbstractOptionCombinedWithPosition; + +import java.time.Duration; +import java.util.Map; + +public class LongPressOptions extends AbstractOptionCombinedWithPosition { + protected Duration duration = null; + + /** + * It creates an empty instance of {@link LongPressOptions}. + * + * @return an empty instance of {@link LongPressOptions} + */ + public static LongPressOptions longPressOptions() { + return new LongPressOptions(); + } + + /** + * Set the long press duration. + * + * @param duration the 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(); + ofNullable(duration).ifPresent(durationParam -> + result.put("duration", durationParam.toMillis())); + return result; + } +} 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..7c4a1e1a0 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/TapOptions.java @@ -0,0 +1,57 @@ +/* + * 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 static com.google.common.base.Preconditions.checkArgument; +import static java.util.Optional.ofNullable; + +import io.appium.java_client.touch.offset.AbstractOptionCombinedWithPosition; + +import java.util.Map; + +public class TapOptions extends AbstractOptionCombinedWithPosition { + private Integer tapsCount = null; + + /** + * It creates an empty instance of {@link TapOptions}. + * + * @return the empty instance of {@link TapOptions} + */ + public static TapOptions tapOptions() { + return new TapOptions(); + } + + /** + * 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(); + ofNullable(tapsCount).ifPresent(integer -> result.put("count", integer)); + 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..422f0b052 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/WaitOptions.java @@ -0,0 +1,65 @@ +/* + * 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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.time.Duration.ofMillis; + +import java.time.Duration; +import java.util.Map; + +public class WaitOptions extends ActionOptions { + protected Duration duration = ofMillis(0); + + /** + * Creates and instance of {@link WaitOptions}. + * + * @param duration is the duration of the waiting. + * @return a built option. + */ + public static WaitOptions waitOptions(Duration duration) { + return new WaitOptions().withDuration(duration); + } + + /** + * Set the wait duration. + * + * @param duration the 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() { + //there is nothing to check + } + + @Override + public Map build() { + final Map result = super.build(); + result.put("ms", duration.toMillis()); + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java b/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java new file mode 100644 index 000000000..a497ab4a8 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/offset/AbstractOptionCombinedWithPosition.java @@ -0,0 +1,50 @@ +package io.appium.java_client.touch.offset; + +import static java.util.Optional.ofNullable; + +import io.appium.java_client.touch.ActionOptions; + +import java.util.Map; + +public abstract class AbstractOptionCombinedWithPosition> + extends ActionOptions> { + private ActionOptions positionOption; + + /** + * Some actions may require coordinates. Invocation of this method + * replaces the result of previous {@link #withElement(ElementOption)} invocation. + * + * @param positionOption required coordinates. * + * @return self-reference + */ + public T withPosition(PointOption positionOption) { + this.positionOption = positionOption; + return (T) this; + } + + /** + * Most of touch action may use position which is relative to some element. In order to unify + * this behaviour this method was added. Invocation of this method + * replaces the result of previous {@link #withPosition(PointOption)} invocation. + * + * @param element required position which is relative to some element + * @return self-reference + */ + public T withElement(ElementOption element) { + positionOption = element; + return (T) this; + } + + protected void verify() { + ofNullable(positionOption).orElseThrow(() -> + new IllegalArgumentException("Some coordinates or an offset from an element should " + + "be defined. Use withPosition or withElement methods")); + } + + @Override + public Map build() { + final Map result = super.build(); + result.putAll(positionOption.build()); + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/offset/ElementOption.java b/src/main/java/io/appium/java_client/touch/offset/ElementOption.java new file mode 100644 index 000000000..0840ea077 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/offset/ElementOption.java @@ -0,0 +1,87 @@ +package io.appium.java_client.touch.offset; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.HashMap; +import java.util.Map; + +public class ElementOption extends PointOption { + + private String elementId; + + /** + * This method creates a build instance of the {@link ElementOption} + * + * @param element is the element to calculate offset from. + * @param x is the x-offset from the upper left corner of the given element. + * @param y is the y-offset from the upper left corner of the given element. + * @return the built option + */ + public static ElementOption element(WebElement element, int x, int y) { + return new ElementOption().withElement(element).withCoordinates(x, y); + } + + /** + * This method creates a build instance of the {@link ElementOption} + * + * @param element is the element to calculate offset from. + * @return the built option + */ + public static ElementOption element(WebElement element) { + return new ElementOption().withElement(element); + } + + /** + * It defines x and y offset from the upper left corner of an element. + * + * @param xOffset is x value. + * @param yOffset is y value. + * @return self-reference + */ + @Override + public ElementOption withCoordinates(int xOffset, int yOffset) { + coordinates = new Point(xOffset, yOffset); + return this; + } + + /** + * This method sets the element as an option. It means that x/y offset is the offset + * from the upper left corner of the given element. + * + * @param element is the element to calculate offset from. + * @return self-reference + */ + public ElementOption withElement(WebElement element) { + checkNotNull(element); + checkArgument(true, "Element should be an instance of the class which " + + "implements org.openqa.selenium.internal.HasIdentity", + (HasIdentity.class.isAssignableFrom(element.getClass()))); + elementId = HasIdentity.class.cast(element).getId(); + return this; + } + + @Override + protected void verify() { + ofNullable(elementId).orElseThrow(() -> + new IllegalArgumentException("Element should be defined")); + } + + @Override + public Map build() { + verify(); + final Map result = new HashMap<>(); + result.put("element", elementId); + + ofNullable(coordinates).ifPresent(point -> { + result.put("x", point.x); + result.put("y", point.y); + }); + return result; + } +} diff --git a/src/main/java/io/appium/java_client/touch/offset/PointOption.java b/src/main/java/io/appium/java_client/touch/offset/PointOption.java new file mode 100644 index 000000000..9eeaaecd9 --- /dev/null +++ b/src/main/java/io/appium/java_client/touch/offset/PointOption.java @@ -0,0 +1,58 @@ +package io.appium.java_client.touch.offset; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.lang.String.format; +import static java.util.Optional.ofNullable; + +import io.appium.java_client.touch.ActionOptions; +import org.openqa.selenium.Point; + +import java.util.Map; + +public class PointOption> extends ActionOptions { + + protected Point coordinates; + + private static final String ERROR_MESSAGE_TEMPLATE = "%s coordinate value should be equal or greater than zero"; + + /** + * It creates a built instance of {@link PointOption} which takes x and y coordinates. + * This is offset from the upper left corner of the screen. + * + * @param xOffset is x value. + * @param yOffset is y value. + * @return a built option + */ + public static PointOption point(int xOffset, int yOffset) { + return new PointOption().withCoordinates(xOffset, yOffset); + } + + /** + * It defines x and y coordinates. + * This is offset from the upper left corner of the screen. + * + * @param xOffset is x value. + * @param yOffset is y value. + * @return self-reference + */ + public T withCoordinates(int xOffset, int yOffset) { + checkArgument(xOffset >= 0, format(ERROR_MESSAGE_TEMPLATE, "X")); + checkArgument(yOffset >= 0, format(ERROR_MESSAGE_TEMPLATE, "Y")); + coordinates = new Point(xOffset, yOffset); + return (T) this; + } + + @Override + protected void verify() { + ofNullable(coordinates).orElseThrow(() -> new IllegalArgumentException( + "Coordinate values must be defined")); + } + + @Override + public Map build() { + final Map result = super.build(); + result.put("x", coordinates.x); + result.put("y", coordinates.y); + 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..f7baf466e 100644 --- a/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidAbilityToUseSupplierTest.java @@ -1,19 +1,21 @@ package io.appium.java_client.android; +import static io.appium.java_client.touch.WaitOptions.waitOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static java.time.Duration.ofSeconds; 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.offset.ElementOption; import org.junit.Test; import org.openqa.selenium.Point; -import java.time.Duration; import java.util.List; 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,23 @@ 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(); + ElementOption pressOption = element(images.get(2),-10,center.y - location.y); + ElementOption moveOption = element(gallery, 10,center.y - location.y); + + return new AndroidTouchAction(driver) + .press(pressOption) + .waitAction(waitOptions(ofSeconds(2))) + .moveTo(moveOption) + .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(element(driver.findElementByAccessibilityId("Gallery"))) + + .waitAction(waitOptions(ofSeconds(2))) + + .moveTo(element(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..a562f273a 100644 --- a/src/test/java/io/appium/java_client/android/AndroidTouchTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidTouchTest.java @@ -1,5 +1,11 @@ package io.appium.java_client.android; +import static io.appium.java_client.touch.LongPressOptions.longPressOptions; +import static io.appium.java_client.touch.TapOptions.tapOptions; +import static io.appium.java_client.touch.WaitOptions.waitOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static io.appium.java_client.touch.offset.PointOption.point; +import static java.time.Duration.ofSeconds; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -12,7 +18,6 @@ import org.openqa.selenium.Point; import org.openqa.selenium.WebElement; -import java.time.Duration; import java.util.List; public class AndroidTouchTest extends BaseAndroidTest { @@ -31,8 +36,10 @@ 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(element(dragDot1)) + .moveTo(element(dragDot3)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -46,8 +53,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, Duration.ofSeconds(2)).moveTo(dragDot3).release(); + TouchAction dragNDrop = new TouchAction(driver) + .longPress(longPressOptions() + .withElement(element(dragDot1)) + .withDuration(ofSeconds(2))) + .moveTo(element(dragDot3)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -64,8 +75,10 @@ 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(point(center1.x, center1.y)) + .moveTo(point(center2.x, center2.y)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -82,9 +95,12 @@ 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(longPressOptions() + .withPosition(point(center1.x, center1.y)) + .withDuration(ofSeconds(2))) + .moveTo(point(center2.x, center2.y)) + .release(); dragNDrop.perform(); assertNotEquals("Drag text empty", "", dragText.getText()); } @@ -94,8 +110,11 @@ 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(point(point.x + 20, point.y + 30)) + .waitAction(waitOptions(ofSeconds(1))) + .release() + .perform(); assertEquals("ON" ,driver .findElementById("io.appium.android.apis:id/button_toggle").getText()); } @@ -103,8 +122,11 @@ 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(element(driver.findElementById("io.appium.android.apis:id/button_toggle"))) + .waitAction(waitOptions(ofSeconds(1))) + .release() + .perform(); assertEquals("ON" ,driver .findElementById("io.appium.android.apis:id/button_toggle").getText()); } @@ -116,8 +138,9 @@ 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(tapOptions().withElement(element(driver.findElementById("io.appium.android.apis:id/start")))) + .waitAction(waitOptions(ofSeconds(2))) + .tap(tapOptions().withElement(element(driver.findElementById("io.appium.android.apis:id/stop")))); startStop.perform(); @@ -136,8 +159,8 @@ 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(point(center1.x, center1.y)) + .tap(element(driver.findElementById("io.appium.android.apis:id/stop"), 5, 5)); startStop.perform(); String time = chronometer.getText(); @@ -157,8 +180,11 @@ 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(element(images.get(2),-10, center.y - location.y)) + .waitAction(waitOptions(ofSeconds(2))) + .moveTo(element(gallery,10,center.y - location.y)) + .release(); swipe.perform(); assertNotEquals(originalImageCount, gallery .findElementsByClassName("android.widget.ImageView").size()); @@ -167,10 +193,12 @@ 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(element(driver.findElementById("io.appium.android.apis:id/button_toggle"))) + .waitAction(waitOptions(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()); diff --git a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java index ddd5b8e8c..e8daa149f 100644 --- a/src/test/java/io/appium/java_client/ios/IOSTouchTest.java +++ b/src/test/java/io/appium/java_client/ios/IOSTouchTest.java @@ -1,21 +1,24 @@ package io.appium.java_client.ios; +import static io.appium.java_client.MobileBy.IosUIAutomation; +import static io.appium.java_client.touch.TapOptions.tapOptions; +import static io.appium.java_client.touch.WaitOptions.waitOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static java.time.Duration.ofSeconds; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; -import io.appium.java_client.MobileBy; import io.appium.java_client.MobileElement; import io.appium.java_client.MultiTouchAction; import io.appium.java_client.TouchAction; +import io.appium.java_client.touch.offset.ElementOption; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.openqa.selenium.Dimension; import org.openqa.selenium.support.ui.WebDriverWait; -import java.time.Duration; - @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class IOSTouchTest extends AppIOSTest { @@ -29,7 +32,7 @@ public void tapTest() { intB.sendKeys("4"); MobileElement e = driver.findElementByAccessibilityId("ComputeSumButton"); - new TouchAction(driver).tap(e).perform(); + new TouchAction(driver).tap(tapOptions().withElement(element(e))).perform(); assertEquals(driver.findElementByXPath("//*[@name = \"Answer\"]").getText(), "6"); } @@ -37,17 +40,23 @@ public void tapTest() { MobileElement slider = driver.findElementByClassName("UIASlider"); Dimension size = slider.getSize(); - TouchAction swipe = new TouchAction(driver).press(slider, size.width / 2 + 2, size.height / 2) - .waitAction(Duration.ofSeconds(2)).moveTo(slider, 1, size.height / 2).release(); + ElementOption press = element(slider, size.width / 2 + 2, size.height / 2); + ElementOption move = element(slider, 1, size.height / 2); + + TouchAction swipe = new TouchAction(driver).press(press) + .waitAction(waitOptions(ofSeconds(2))) + .moveTo(move).release(); + swipe.perform(); assertEquals("0%", slider.getAttribute("value")); } @Test public void multiTouchTest() { MobileElement e = driver.findElementByAccessibilityId("ComputeSumButton"); - TouchAction tap1 = new TouchAction(driver).tap(e); - TouchAction tap2 = new TouchAction(driver).tap(driver.findElement(MobileBy - .IosUIAutomation(".elements().withName(\"show alert\")"))); + MobileElement e2 = driver.findElement(IosUIAutomation(".elements().withName(\"show alert\")")); + + TouchAction tap1 = new TouchAction(driver).tap(tapOptions().withElement(element(e))); + TouchAction tap2 = new TouchAction(driver).tap(tapOptions().withElement(element(e2))); new MultiTouchAction(driver).add(tap1).add(tap2).perform(); diff --git a/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java b/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java index 907a3a746..03fd20358 100644 --- a/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java +++ b/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java @@ -16,6 +16,7 @@ package io.appium.java_client.ios; +import static io.appium.java_client.touch.offset.ElementOption.element; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; @@ -48,15 +49,11 @@ public class XCUIAutomationTest extends AppXCUITTest { assertEquals(driver.rotation(), landscapeLeftRotation); } - @Test public void testTouchId() { - try { - driver.toggleTouchIDEnrollment(true); - driver.performTouchID(true); - driver.performTouchID(false); - assertEquals(true, true); - } catch (Exception e) { - throw e; - } + @Test public void testTouchId() throws Exception { + driver.toggleTouchIDEnrollment(true); + driver.performTouchID(true); + driver.performTouchID(false); + assertEquals(true, true); } @Test public void testPutIntoBackgroundAndRestore() { @@ -76,7 +73,7 @@ public class XCUIAutomationTest extends AppXCUITTest { firstField.sendKeys("2"); IOSTouchAction iosTouchAction = new IOSTouchAction(driver); - iosTouchAction.doubleTap(firstField); + iosTouchAction.doubleTap(element(firstField)); IOSElement editingMenu = driver.findElementByClassName("UIAEditingMenu"); assertNotNull(editingMenu); } diff --git a/src/test/java/io/appium/java_client/touch/DummyElement.java b/src/test/java/io/appium/java_client/touch/DummyElement.java new file mode 100644 index 000000000..cf98bcb55 --- /dev/null +++ b/src/test/java/io/appium/java_client/touch/DummyElement.java @@ -0,0 +1,103 @@ +package io.appium.java_client.touch; + +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.Point; +import org.openqa.selenium.Rectangle; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.List; + +public class DummyElement implements WebElement, HasIdentity { + @Override + public void click() { + // dummy + } + + @Override + public void submit() { + // dummy + } + + @Override + public void sendKeys(CharSequence... charSequences) { + // dummy + } + + @Override + public void clear() { + // dummy + } + + @Override + public String getTagName() { + return ""; + } + + @Override + public String getAttribute(String s) { + return ""; + } + + @Override + public boolean isSelected() { + return false; + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public String getText() { + return ""; + } + + @Override + public List findElements(By by) { + return null; + } + + @Override + public WebElement findElement(By by) { + return null; + } + + @Override + public boolean isDisplayed() { + return false; + } + + @Override + public Point getLocation() { + return null; + } + + @Override + public Dimension getSize() { + return null; + } + + @Override + public Rectangle getRect() { + return null; + } + + @Override + public String getCssValue(String s) { + return ""; + } + + @Override + public X getScreenshotAs(OutputType outputType) { + return null; + } + + @Override + public String getId() { + return "123"; + } +} \ No newline at end of file diff --git a/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java b/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java new file mode 100644 index 000000000..93c1dd804 --- /dev/null +++ b/src/test/java/io/appium/java_client/touch/FailsWithMatcher.java @@ -0,0 +1,43 @@ +package io.appium.java_client.touch; + +import static org.hamcrest.core.AllOf.allOf; +import static org.hamcrest.core.IsInstanceOf.instanceOf; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +public final class FailsWithMatcher extends TypeSafeMatcher { + + private final Matcher matcher; + + private FailsWithMatcher(final Matcher matcher) { + this.matcher = matcher; + } + + public static Matcher failsWith( + final Class throwableType) { + return new FailsWithMatcher<>(instanceOf(throwableType)); + } + + public static Matcher failsWith( + final Class throwableType, final Matcher throwableMatcher) { + return new FailsWithMatcher<>(allOf(instanceOf(throwableType), throwableMatcher)); + } + + @Override + protected boolean matchesSafely(final Runnable runnable) { + try { + runnable.run(); + return false; + } catch (final Throwable ex) { + return matcher.matches(ex); + } + } + + @Override + public void describeTo(final Description description) { + description.appendText("fails with ").appendDescriptionOf(matcher); + } + +} diff --git a/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java new file mode 100644 index 000000000..9822b0c9e --- /dev/null +++ b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java @@ -0,0 +1,97 @@ +package io.appium.java_client.touch; + +import static io.appium.java_client.touch.FailsWithMatcher.failsWith; +import static io.appium.java_client.touch.LongPressOptions.longPressOptions; +import static io.appium.java_client.touch.TapOptions.tapOptions; +import static io.appium.java_client.touch.WaitOptions.waitOptions; +import static io.appium.java_client.touch.offset.ElementOption.element; +import static io.appium.java_client.touch.offset.PointOption.point; +import static java.time.Duration.ofMillis; +import static java.time.Duration.ofSeconds; +import static junit.framework.TestCase.fail; +import static org.hamcrest.CoreMatchers.everyItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isIn; + +import io.appium.java_client.touch.offset.ElementOption; +import io.appium.java_client.touch.offset.PointOption; +import org.junit.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TouchOptionsTests { + private static final WebElement DUMMY_ELEMENT = new DummyElement(); + + @Test(expected = IllegalArgumentException.class) + public void invalidEmptyPointOptionsShouldFailOnBuild() throws Exception { + new PointOption().build(); + fail("The exception throwing was expected"); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidEmptyElementOptionsShouldFailOnBuild() throws Exception { + new ElementOption().build(); + fail("The exception throwing was expected"); + } + + @Test + public void invalidOptionsArgumentsShouldFailOnAltering() throws Exception { + final List invalidOptions = new ArrayList<>(); + invalidOptions.add(() -> waitOptions(ofMillis(-1))); + invalidOptions.add(() -> new ElementOption().withCoordinates(0, 0).withElement(null)); + invalidOptions.add(() -> new PointOption().withCoordinates(0, -1)); + invalidOptions.add(() -> new PointOption().withCoordinates(-1, 0)); + invalidOptions.add(() -> new WaitOptions().withDuration(null)); + invalidOptions.add(() -> tapOptions().withTapsCount(-1)); + invalidOptions.add(() -> longPressOptions().withDuration(null)); + invalidOptions.add(() -> longPressOptions().withDuration(ofMillis(-1))); + for (Runnable item : invalidOptions) { + assertThat(item, failsWith(RuntimeException.class)); + } + } + + @Test + public void longPressOptionsShouldBuildProperly() throws Exception { + final Map actualOpts = longPressOptions() + .withElement(element(DUMMY_ELEMENT).withCoordinates(0, 0)) + .withDuration(ofMillis(1)) + .build(); + final Map expectedOpts = new HashMap<>(); + expectedOpts.put("element", ((HasIdentity) DUMMY_ELEMENT).getId()); + expectedOpts.put("x", 0); + expectedOpts.put("y", 0); + expectedOpts.put("duration", 1L); + assertThat(actualOpts.entrySet(), everyItem(isIn(expectedOpts.entrySet()))); + assertThat(expectedOpts.entrySet(), everyItem(isIn(actualOpts.entrySet()))); + } + + @Test + public void tapOptionsShouldBuildProperly() throws Exception { + final Map actualOpts = tapOptions() + .withPosition(point(0, 0)) + .withTapsCount(2) + .build(); + final Map expectedOpts = new HashMap<>(); + expectedOpts.put("x", 0); + expectedOpts.put("y", 0); + expectedOpts.put("count", 2); + assertThat(actualOpts.entrySet(), everyItem(isIn(expectedOpts.entrySet()))); + assertThat(expectedOpts.entrySet(), everyItem(isIn(actualOpts.entrySet()))); + } + + @Test + public void waitOptionsShouldBuildProperly() throws Exception { + final Map actualOpts = new WaitOptions() + .withDuration(ofSeconds(1)) + .build(); + final Map expectedOpts = new HashMap<>(); + expectedOpts.put("ms", 1000L); + assertThat(actualOpts.entrySet(), everyItem(isIn(expectedOpts.entrySet()))); + assertThat(expectedOpts.entrySet(), everyItem(isIn(actualOpts.entrySet()))); + } +}