diff --git a/src/main/java/io/appium/java_client/TouchAction.java b/src/main/java/io/appium/java_client/TouchAction.java index ca30fde11..d35d07221 100644 --- a/src/main/java/io/appium/java_client/TouchAction.java +++ b/src/main/java/io/appium/java_client/TouchAction.java @@ -16,11 +16,18 @@ package io.appium.java_client; +import static com.google.common.base.Preconditions.checkNotNull; + 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; @@ -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..3a03dd311 --- /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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.time.Duration; +import java.util.Map; + +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..56d13bfa8 --- /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 static com.google.common.base.Preconditions.checkNotNull; + +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.Map; + +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 be defined if 'element' option is not set"); + } + if (relativeOffset != null) { + throw new IllegalArgumentException( + "Relative offset must not be defined if 'element' option is not set"); + } + } else { + if (absoluteOffset != null) { + throw new IllegalArgumentException( + "Absolute offset must not be defined if 'element' option 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..8046f982d --- /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 static com.google.common.base.Preconditions.checkNotNull; + +import org.openqa.selenium.Point; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import java.util.Map; + +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..ae2c777c4 --- /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 static com.google.common.base.Preconditions.checkArgument; + +import java.util.Map; + +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..e5a2d6be8 --- /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 static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.time.Duration; +import java.util.Map; + +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()); 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..345409054 --- /dev/null +++ b/src/test/java/io/appium/java_client/touch/DummyElement.java @@ -0,0 +1,104 @@ +package io.appium.java_client.touch; + + +import org.openqa.selenium.By; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.Point; +import org.openqa.selenium.Rectangle; +import org.openqa.selenium.OutputType; +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..edd16e026 --- /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 IThrowingRunnable 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/IThrowingRunnable.java b/src/test/java/io/appium/java_client/touch/IThrowingRunnable.java new file mode 100644 index 000000000..5ab472f89 --- /dev/null +++ b/src/test/java/io/appium/java_client/touch/IThrowingRunnable.java @@ -0,0 +1,6 @@ +package io.appium.java_client.touch; + +@FunctionalInterface +public interface IThrowingRunnable { + void run() throws E; +} 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..1ed86199f --- /dev/null +++ b/src/test/java/io/appium/java_client/touch/TouchOptionsTests.java @@ -0,0 +1,124 @@ +package io.appium.java_client.touch; + +import org.junit.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.internal.HasIdentity; + +import static io.appium.java_client.touch.FailsWithMatcher.failsWith; +import static org.hamcrest.CoreMatchers.everyItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isIn; + +import java.time.Duration; +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 + public void invalidAbsolutePositionOptionsShouldFailOnBuild() throws Exception { + final List invalidOptions = new ArrayList<>(); + invalidOptions.add(new PressOptions() + .withElement(DUMMY_ELEMENT) + .withAbsoluteOffset(0, 0)); + invalidOptions.add(new LongPressOptions() + .withRelativeOffset(0, 0)); + invalidOptions.add(new TapOptions()); + invalidOptions.add(new TapOptions() + .withAbsoluteOffset(0, 0) + .withRelativeOffset(0, 0)); + for (ActionOptions opts : invalidOptions) { + assertThat(opts::build, failsWith(IllegalArgumentException.class)); + } + } + + @Test + public void invalidRelativePositionOptionsShouldFailOnBuild() throws Exception { + final List invalidOptions = new ArrayList<>(); + invalidOptions.add(new MoveToOptions()); + for (ActionOptions opts : invalidOptions) { + assertThat(opts::build, failsWith(IllegalArgumentException.class)); + } + } + + @Test + public void invalidOptionsArgumentsShouldFailOnAltering() throws Exception { + final List> invalidOptions = new ArrayList<>(); + invalidOptions.add(() -> new WaitOptions().withDuration(Duration.ofMillis(-1))); + invalidOptions.add(() -> new PressOptions().withElement(null)); + invalidOptions.add(() -> new MoveToOptions().withElement(null)); + invalidOptions.add(() -> new WaitOptions().withDuration(null)); + for (IThrowingRunnable item : invalidOptions) { + assertThat(item, failsWith(RuntimeException.class)); + } + } + + @Test + public void longPressOptionsShouldBuildProperly() throws Exception { + final Map actualOpts = new LongPressOptions() + .withElement(DUMMY_ELEMENT) + .withRelativeOffset(0, 0) + .withDuration(Duration.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 = new TapOptions() + .withAbsoluteOffset(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 pressOptionsShouldBuildProperly() throws Exception { + final Map actualOpts = new PressOptions() + .withElement(DUMMY_ELEMENT) + .build(); + final Map expectedOpts = new HashMap<>(); + expectedOpts.put("element", ((HasIdentity) DUMMY_ELEMENT).getId()); + assertThat(actualOpts.entrySet(), everyItem(isIn(expectedOpts.entrySet()))); + assertThat(expectedOpts.entrySet(), everyItem(isIn(actualOpts.entrySet()))); + } + + @Test + public void moveToOptionsShouldBuildProperly() throws Exception { + final Map actualOpts = new MoveToOptions() + .withElement(DUMMY_ELEMENT) + .withRelativeOffset(-1,-1) + .build(); + final Map expectedOpts = new HashMap<>(); + expectedOpts.put("element", ((HasIdentity) DUMMY_ELEMENT).getId()); + expectedOpts.put("x", -1); + expectedOpts.put("y", -1); + 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(Duration.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()))); + } +}