Skip to content

Commit 612ba96

Browse files
pujaganidiemol
andauthored
[java] Add wheel input (#10445)
* [java] Add wheel input * [java] Use a descriptive assert method for tests * [java] Add scroll wheel method * [java] Chain assert statements * [java] Add an exception when JSON Wire Protocol is used with scroll method * [java] Revert adding the error message for JWP * Remove passing duration explicitly Co-authored-by: Diego Molina <[email protected]>
1 parent 015c939 commit 612ba96

6 files changed

Lines changed: 456 additions & 3 deletions

File tree

java/src/org/openqa/selenium/interactions/Actions.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package org.openqa.selenium.interactions;
1919

20-
import static org.openqa.selenium.interactions.PointerInput.Kind.MOUSE;
2120
import static org.openqa.selenium.interactions.PointerInput.MouseButton.LEFT;
2221
import static org.openqa.selenium.interactions.PointerInput.MouseButton.RIGHT;
2322

@@ -58,6 +57,7 @@ public class Actions {
5857
private final Map<InputSource, Sequence> sequences = new HashMap<>();
5958
private PointerInput activePointer;
6059
private KeyInput activeKeyboard;
60+
private WheelInput activeWheel;
6161

6262
// JSON-wire protocol
6363
private final Keyboard jsonKeyboard;
@@ -271,6 +271,10 @@ public Actions release(WebElement target) {
271271
return moveInTicks(target, 0, 0).tick(getActivePointer().createPointerUp(LEFT.asArg()));
272272
}
273273

274+
public Actions scroll( int x, int y, int deltaX, int deltaY, Origin origin) {
275+
return tick(getActiveWheel().createScroll(x, y, deltaX, deltaY, Duration.ofMillis(250), origin));
276+
}
277+
274278
/**
275279
* Releases the depressed left mouse button at the current mouse location.
276280
* @see #release(org.openqa.selenium.WebElement)
@@ -574,6 +578,22 @@ public Actions setActivePointer(PointerInput.Kind kind, String name) {
574578
return this;
575579
}
576580

581+
public Actions setActiveWheel(String name) {
582+
InputSource inputSource = sequences.keySet()
583+
.stream()
584+
.filter(input -> Objects.equals(input.getName(), name))
585+
.findFirst()
586+
.orElse(null);
587+
588+
if (inputSource == null) {
589+
this.activeWheel = new WheelInput(name);
590+
} else {
591+
this.activeWheel = (WheelInput) inputSource;
592+
}
593+
594+
return this;
595+
}
596+
577597
public KeyInput getActiveKeyboard() {
578598
if (this.activeKeyboard == null) {
579599
setActiveKeyboard("default keyboard");
@@ -588,6 +608,13 @@ public PointerInput getActivePointer() {
588608
return this.activePointer;
589609
}
590610

611+
public WheelInput getActiveWheel() {
612+
if (this.activeWheel == null) {
613+
setActiveWheel("default wheel");
614+
}
615+
return this.activeWheel;
616+
}
617+
591618
/**
592619
* Generates a composite action containing all actions so far, ready to be performed (and
593620
* resets the internal builder state, so subsequent calls to this method will contain fresh

java/src/org/openqa/selenium/interactions/SourceType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
public enum SourceType {
2424
KEY("key"),
2525
NONE(null),
26-
POINTER("pointer");
26+
POINTER("pointer"),
27+
WHEEL("wheel");
2728

2829
private final String type;
2930

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.interactions;
19+
20+
import static org.openqa.selenium.internal.Require.nonNegative;
21+
22+
import java.time.Duration;
23+
import java.util.HashMap;
24+
import java.util.Map;
25+
import java.util.Optional;
26+
import java.util.UUID;
27+
28+
import org.openqa.selenium.interactions.PointerInput.Origin;
29+
import org.openqa.selenium.internal.Require;
30+
31+
/**
32+
* Models a <a href="https://www.w3.org/TR/webdriver/#dfn-wheel-input-source">wheel input source</a>.
33+
*/
34+
public class WheelInput implements InputSource, Encodable {
35+
36+
private final String name;
37+
38+
public WheelInput(String name) {
39+
this.name = Optional.ofNullable(name).orElse(UUID.randomUUID().toString());
40+
}
41+
42+
@Override
43+
public String getName() {
44+
return this.name;
45+
}
46+
47+
@Override
48+
public SourceType getInputType() {
49+
return SourceType.WHEEL;
50+
}
51+
52+
public Interaction createScroll(
53+
int x, int y, int deltaX, int deltaY, Duration duration, Origin origin) {
54+
return new ScrollInteraction(this, x, y, deltaX, deltaY, duration, origin);
55+
}
56+
57+
@Override
58+
public Map<String, Object> encode() {
59+
Map<String, Object> toReturn = new HashMap<>();
60+
61+
toReturn.put("type", getInputType().getType());
62+
toReturn.put("id", this.name);
63+
64+
return toReturn;
65+
}
66+
67+
static class ScrollInteraction extends Interaction implements Encodable {
68+
69+
private final int x;
70+
private final int y;
71+
private final int deltaX;
72+
private final int deltaY;
73+
private final Duration duration;
74+
private final Origin origin;
75+
76+
protected ScrollInteraction(
77+
InputSource source,
78+
int x,
79+
int y,
80+
int deltaX,
81+
int deltaY,
82+
Duration duration,
83+
Origin origin) {
84+
super(source);
85+
86+
this.x = x;
87+
this.y = y;
88+
this.deltaX = deltaX;
89+
this.deltaY = deltaY;
90+
this.duration = nonNegative(duration);
91+
this.origin = Require.nonNull("Origin of scroll", origin);
92+
Require.precondition(!origin.asArg().equals("pointer"),
93+
"Wheel input only accepts viewport or element origin." );
94+
}
95+
96+
@Override
97+
public Map<String, Object> encode() {
98+
Map<String, Object> toReturn = new HashMap<>();
99+
100+
toReturn.put("type", "scroll");
101+
toReturn.put("x", x);
102+
toReturn.put("y", y);
103+
toReturn.put("deltaX", deltaX);
104+
toReturn.put("deltaY", deltaY);
105+
toReturn.put("duration", duration.toMillis());
106+
toReturn.put("origin", origin.asArg());
107+
108+
return toReturn;
109+
}
110+
}
111+
}

java/test/org/openqa/selenium/interactions/ActionsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.openqa.selenium.WebElement;
4040
import org.openqa.selenium.testing.UnitTests;
4141

42+
import java.time.Duration;
4243
import java.util.Collection;
4344
import java.util.HashMap;
4445
import java.util.List;
@@ -220,7 +221,6 @@ public void testCtrlClick() {
220221
.containsEntry("type", "keyUp").containsEntry("value", CONTROL.toString());
221222
}
222223

223-
224224
private WebElement mockLocatableElementWithCoordinates(Coordinates coordinates) {
225225
WebElement element = mock(WebElement.class,
226226
withSettings().extraInterfaces(Locatable.class));
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.openqa.selenium.interactions;
19+
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertTrue;
22+
23+
import org.junit.Test;
24+
import org.openqa.selenium.By;
25+
import org.openqa.selenium.JavascriptExecutor;
26+
import org.openqa.selenium.WebDriver;
27+
import org.openqa.selenium.WebElement;
28+
import org.openqa.selenium.testing.JUnit4TestBase;
29+
30+
import java.time.Duration;
31+
32+
/**
33+
* Tests operations that involve scroll wheel.
34+
*/
35+
public class DefaultWheelTest extends JUnit4TestBase {
36+
37+
private Actions getBuilder(WebDriver driver) {
38+
return new Actions(driver);
39+
}
40+
41+
@Test
42+
public void shouldScrollToElement() {
43+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html"));
44+
WebElement iframe = driver.findElement(By.tagName("iframe"));
45+
46+
assertFalse(inViewport(iframe));
47+
48+
getBuilder(driver).scroll(
49+
0,
50+
0,
51+
0,
52+
0,
53+
PointerInput.Origin.fromElement(iframe)).perform();
54+
55+
assertTrue(inViewport(iframe));
56+
}
57+
58+
@Test
59+
public void shouldScrollFromElementByGivenAmount() {
60+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html"));
61+
WebElement iframe = driver.findElement(By.tagName("iframe"));
62+
63+
getBuilder(driver).scroll(
64+
0,
65+
0,
66+
0,
67+
200,
68+
PointerInput.Origin.fromElement(iframe)).perform();
69+
70+
driver.switchTo().frame(iframe);
71+
72+
WebElement checkbox = driver.findElement(By.name("scroll_checkbox"));
73+
assertTrue(inViewport(checkbox));
74+
}
75+
76+
@Test
77+
public void shouldScrollFromElementByGivenAmountWithOffset() {
78+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html"));
79+
WebElement footer = driver.findElement(By.tagName("footer"));
80+
81+
getBuilder(driver).scroll(
82+
0,
83+
-50,
84+
0,
85+
200,
86+
PointerInput.Origin.fromElement(footer)).perform();
87+
88+
driver.switchTo().frame(driver.findElement(By.tagName("iframe")));
89+
90+
WebElement checkbox = driver.findElement(By.name("scroll_checkbox"));
91+
assertTrue(inViewport(checkbox));
92+
}
93+
94+
@Test(expected = MoveTargetOutOfBoundsException.class)
95+
public void throwErrorWhenElementOriginIsOutOfViewport() {
96+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html"));
97+
WebElement footer = driver.findElement(By.tagName("footer"));
98+
99+
getBuilder(driver).scroll(
100+
0,
101+
50,
102+
0,
103+
200,
104+
PointerInput.Origin.fromElement(footer)).perform();
105+
}
106+
107+
@Test
108+
public void shouldScrollFromViewportByGivenAmount() {
109+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html"));
110+
WebElement footer = driver.findElement(By.tagName("footer"));
111+
112+
int y = footer.getRect().y;
113+
114+
getBuilder(driver).scroll(
115+
0,
116+
0,
117+
0,
118+
y,
119+
PointerInput.Origin.viewport()).perform();
120+
121+
assertTrue(inViewport(footer));
122+
}
123+
124+
@Test
125+
public void shouldScrollFromViewportByGivenAmountFromOrigin() {
126+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame.html"));
127+
128+
WebElement iframe = driver.findElement(By.tagName("iframe"));
129+
130+
getBuilder(driver).scroll(
131+
10,
132+
10,
133+
0,
134+
200,
135+
PointerInput.Origin.viewport()).perform();
136+
137+
driver.switchTo().frame(iframe);
138+
139+
WebElement checkbox = driver.findElement(By.name("scroll_checkbox"));
140+
assertTrue(inViewport(checkbox));
141+
}
142+
143+
@Test(expected = MoveTargetOutOfBoundsException.class)
144+
public void throwErrorWhenOriginOffsetIsOutOfViewport() {
145+
driver.get(appServer.whereIs("scrolling_tests/frame_with_nested_scrolling_frame.html"));
146+
147+
getBuilder(driver).scroll(
148+
-10,
149+
-10,
150+
0,
151+
200,
152+
PointerInput.Origin.viewport()).perform();
153+
}
154+
155+
private boolean inViewport(WebElement element) {
156+
157+
String script =
158+
"for(var e=arguments[0],f=e.offsetTop,t=e.offsetLeft,o=e.offsetWidth,n=e.offsetHeight;\n"
159+
+ "e.offsetParent;)f+=(e=e.offsetParent).offsetTop,t+=e.offsetLeft;\n"
160+
+ "return f<window.pageYOffset+window.innerHeight&&t<window.pageXOffset+window.innerWidth&&f+n>\n"
161+
+ "window.pageYOffset&&t+o>window.pageXOffset";
162+
163+
return (boolean) ((JavascriptExecutor) driver).executeScript(script, element);
164+
}
165+
}

0 commit comments

Comments
 (0)