Skip to content

Commit 08052f7

Browse files
committed
[dotnet] implement scroll action
1 parent b65c6a4 commit 08052f7

4 files changed

Lines changed: 209 additions & 8 deletions

File tree

dotnet/src/webdriver/Interactions/Actions.cs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
using System;
2020
using System.Collections.Generic;
2121
using System.Drawing;
22-
using OpenQA.Selenium.Internal;
2322

2423
namespace OpenQA.Selenium.Interactions
2524
{
@@ -35,7 +34,7 @@ public enum MoveToElementOffsetOrigin
3534
TopLeft,
3635

3736
/// <summary>
38-
/// Offsets are calcuated from the center of the element.
37+
/// Offsets are calculated from the center of the element.
3938
/// </summary>
4039
Center
4140
}
@@ -45,6 +44,7 @@ public enum MoveToElementOffsetOrigin
4544
/// </summary>
4645
public class Actions : IAction
4746
{
47+
private readonly TimeSpan DefaultScrollDuration = TimeSpan.FromMilliseconds(250);
4848
private readonly TimeSpan DefaultMouseMoveDuration = TimeSpan.FromMilliseconds(250);
4949
private ActionBuilder actionBuilder = new ActionBuilder();
5050
private PointerInputDevice defaultMouse = new PointerInputDevice(PointerKind.Mouse, "default mouse");
@@ -405,6 +405,44 @@ public Actions DragAndDropToOffset(IWebElement source, int offsetX, int offsetY)
405405
return this;
406406
}
407407

408+
/// <summary>
409+
/// Scrolls by the provided amount from a designated origination point.
410+
/// </summary>
411+
/// <remarks>
412+
/// The scroll origin is either the center of an element or the upper left of the viewport plus any offsets.
413+
/// If the origin is an element, and the element is not in the viewport, the bottom of the element will first
414+
/// be scrolled to the bottom of the viewport.
415+
/// </remarks>
416+
/// <param name="xOffset">The horizontal offset from the origin from which to start the scroll.</param>
417+
/// <param name="yOffset">The vertical offset from the origin from which to start the scroll.</param>
418+
/// <param name="deltaX">Distance along X axis to scroll using the wheel. A negative value scrolls left.</param>
419+
/// <param name="deltaY">Distance along Y axis to scroll using the wheel. A negative value scrolls up.</param>
420+
/// <param name="scrollOrigin">Where scroll originates (viewport or element center).</param>
421+
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
422+
/// <exception cref="MoveTargetOutOfBoundsException">If origin plus offsets is outside viewport.</exception>
423+
public Actions Scroll(int xOffset, int yOffset, int deltaX, int deltaY, WheelInputDevice.ScrollOrigin scrollOrigin)
424+
{
425+
WheelInputDevice wheel = new WheelInputDevice();
426+
427+
if (scrollOrigin.Viewport && scrollOrigin.Element != null)
428+
{
429+
throw new ArgumentException("viewport can not be true if an element is defined.", nameof(scrollOrigin));
430+
}
431+
432+
if (scrollOrigin.Viewport)
433+
{
434+
this.actionBuilder.AddAction(wheel.CreateWheelScroll(CoordinateOrigin.Viewport,
435+
xOffset, yOffset, deltaX, deltaY, DefaultScrollDuration));
436+
}
437+
else
438+
{
439+
this.actionBuilder.AddAction(wheel.CreateWheelScroll(scrollOrigin.Element,
440+
xOffset, yOffset, deltaX, deltaY, DefaultScrollDuration));
441+
}
442+
443+
return this;
444+
}
445+
408446
/// <summary>
409447
/// Builds the sequence of actions.
410448
/// </summary>

dotnet/src/webdriver/Interactions/WheelInputDevice.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@
1919
using OpenQA.Selenium.Internal;
2020
using System;
2121
using System.Collections.Generic;
22-
using System.Linq;
23-
using System.Text;
24-
using System.Threading.Tasks;
2522

2623
namespace OpenQA.Selenium.Interactions
2724
{
@@ -75,7 +72,7 @@ public override Dictionary<string, object> ToDictionary()
7572
/// <param name="deltaX">The distance along the X axis to scroll using the wheel.</param>
7673
/// <param name="deltaY">The distance along the Y axis to scroll using the wheel.</param>
7774
/// <param name="duration">The duration of the scroll action.</param>
78-
/// <returns></returns>
75+
/// <returns>The <see cref="Interaction"/> representing the wheel scroll.</returns>
7976
public Interaction CreateWheelScroll(int deltaX, int deltaY, TimeSpan duration)
8077
{
8178
return new WheelScrollInteraction(this, null, CoordinateOrigin.Viewport, 0, 0, deltaX, deltaY, duration);
@@ -90,7 +87,7 @@ public Interaction CreateWheelScroll(int deltaX, int deltaY, TimeSpan duration)
9087
/// <param name="deltaX">The distance along the X axis to scroll using the wheel.</param>
9188
/// <param name="deltaY">The distance along the Y axis to scroll using the wheel.</param>
9289
/// <param name="duration">The duration of the scroll action.</param>
93-
/// <returns></returns>
90+
/// <returns>The <see cref="Interaction"/> representing the wheel scroll.</returns>
9491
public Interaction CreateWheelScroll(IWebElement target, int xOffset, int yOffset, int deltaX, int deltaY, TimeSpan duration)
9592
{
9693
return new WheelScrollInteraction(this, target, CoordinateOrigin.Element, xOffset, yOffset, deltaX, deltaY, duration);
@@ -105,12 +102,30 @@ public Interaction CreateWheelScroll(IWebElement target, int xOffset, int yOffse
105102
/// <param name="deltaX">The distance along the X axis to scroll using the wheel.</param>
106103
/// <param name="deltaY">The distance along the Y axis to scroll using the wheel.</param>
107104
/// <param name="duration">The duration of the scroll action.</param>
108-
/// <returns></returns>
105+
/// <returns>The <see cref="Interaction"/> representing the wheel scroll.</returns>
109106
public Interaction CreateWheelScroll(CoordinateOrigin origin, int xOffset, int yOffset, int deltaX, int deltaY, TimeSpan duration)
110107
{
111108
return new WheelScrollInteraction(this, null, origin, xOffset, yOffset, deltaX, deltaY, duration);
112109
}
113110

111+
public class ScrollOrigin
112+
{
113+
private IWebElement element;
114+
private bool viewport;
115+
116+
public IWebElement Element
117+
{
118+
get { return this.element; }
119+
set { this.element = value; }
120+
}
121+
122+
public bool Viewport
123+
{
124+
get { return this.viewport; }
125+
set { this.viewport = value; }
126+
}
127+
}
128+
114129
private class WheelScrollInteraction : Interaction
115130
{
116131
private IWebElement target;

dotnet/test/common/DriverTestFixture.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ public abstract class DriverTestFixture
8181
public string authenticationPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("basicAuth");
8282
public string html5Page = EnvironmentManager.Instance.UrlBuilder.WhereIs("html5Page.html");
8383
public string shadowRootPage = EnvironmentManager.Instance.UrlBuilder.WhereIs("shadowRootPage.html");
84+
public string scrollFrameOutOfViewport = EnvironmentManager.Instance.UrlBuilder.WhereIs("scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html");
85+
public string scrollFrameInViewport = EnvironmentManager.Instance.UrlBuilder.WhereIs("scrolling_tests/frame_with_nested_scrolling_frame.html");
8486

8587
protected IWebDriver driver;
8688

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System;
2+
using NUnit.Framework;
3+
4+
namespace OpenQA.Selenium.Interactions
5+
{
6+
[TestFixture]
7+
public class BasicWheelInterfaceTest : DriverTestFixture
8+
{
9+
[SetUp]
10+
public void SetupTest()
11+
{
12+
IActionExecutor actionExecutor = driver as IActionExecutor;
13+
if (actionExecutor != null)
14+
{
15+
actionExecutor.ResetInputState();
16+
}
17+
driver.SwitchTo().DefaultContent();
18+
((IJavaScriptExecutor)driver).ExecuteScript("window.scrollTo(0, 0)");
19+
}
20+
21+
[Test]
22+
public void ShouldAllowScrollingToAnElement()
23+
{
24+
driver.Url = scrollFrameOutOfViewport;
25+
IWebElement iframe = driver.FindElement(By.TagName("iframe"));
26+
27+
Assert.IsFalse(IsInViewport(iframe));
28+
29+
var scrollOrigin = new WheelInputDevice.ScrollOrigin
30+
{
31+
Element = iframe
32+
};
33+
34+
new Actions(driver).Scroll(0, 0, 0, 0, scrollOrigin).Build().Perform();
35+
36+
Assert.IsTrue(IsInViewport(iframe));
37+
}
38+
39+
[Test]
40+
public void ShouldScrollFromElementByGivenAmount()
41+
{
42+
driver.Url = scrollFrameOutOfViewport;
43+
IWebElement iframe = driver.FindElement(By.TagName("iframe"));
44+
WheelInputDevice.ScrollOrigin scrollOrigin = new WheelInputDevice.ScrollOrigin
45+
{
46+
Element = iframe
47+
};
48+
49+
new Actions(driver).Scroll(0, 0, 0, 200, scrollOrigin).Build().Perform();
50+
51+
driver.SwitchTo().Frame(iframe);
52+
IWebElement checkbox = driver.FindElement(By.Name("scroll_checkbox"));
53+
Assert.IsTrue(IsInViewport(checkbox));
54+
}
55+
56+
[Test]
57+
public void ShouldAllowScrollingFromElementByGivenAmountWithOffset()
58+
{
59+
driver.Url = scrollFrameOutOfViewport;
60+
IWebElement footer = driver.FindElement(By.TagName("footer"));
61+
var scrollOrigin = new WheelInputDevice.ScrollOrigin
62+
{
63+
Element = footer
64+
};
65+
66+
new Actions(driver).Scroll(0, -50, 0, 200, scrollOrigin).Build().Perform();
67+
68+
IWebElement iframe = driver.FindElement(By.TagName("iframe"));
69+
driver.SwitchTo().Frame(iframe);
70+
IWebElement checkbox = driver.FindElement(By.Name("scroll_checkbox"));
71+
Assert.IsTrue(IsInViewport(checkbox));
72+
}
73+
74+
[Test]
75+
public void ShouldNotAllowScrollingWhenElementOriginOutOfViewport()
76+
{
77+
driver.Url = scrollFrameOutOfViewport;
78+
IWebElement footer = driver.FindElement(By.TagName("footer"));
79+
var scrollOrigin = new WheelInputDevice.ScrollOrigin
80+
{
81+
Element = footer
82+
};
83+
84+
Assert.That(() => new Actions(driver).Scroll(0, 50, 0, 0, scrollOrigin).Build().Perform(),
85+
Throws.InstanceOf<MoveTargetOutOfBoundsException>());
86+
}
87+
88+
[Test]
89+
public void ShouldAllowScrollingFromViewportByGivenAmount()
90+
{
91+
driver.Url = scrollFrameOutOfViewport;
92+
IWebElement footer = driver.FindElement(By.TagName("footer"));
93+
int deltaY = footer.Location.Y;
94+
var scrollOrigin = new WheelInputDevice.ScrollOrigin
95+
{
96+
Viewport = true
97+
};
98+
99+
new Actions(driver).Scroll(0, 0, 0, deltaY, scrollOrigin).Build().Perform();
100+
101+
Assert.IsTrue(IsInViewport(footer));
102+
}
103+
104+
[Test]
105+
public void ShouldAllowScrollingFromViewportByGivenAmountFromOrigin()
106+
{
107+
driver.Url = scrollFrameInViewport;
108+
var scrollOrigin = new WheelInputDevice.ScrollOrigin
109+
{
110+
Viewport = true
111+
};
112+
113+
new Actions(driver).Scroll(10, 10, 0, 200, scrollOrigin).Build().Perform();
114+
115+
IWebElement iframe = driver.FindElement(By.TagName("iframe"));
116+
driver.SwitchTo().Frame(iframe);
117+
IWebElement checkbox = driver.FindElement(By.Name("scroll_checkbox"));
118+
Assert.IsTrue(IsInViewport(checkbox));
119+
}
120+
121+
[Test]
122+
public void ShouldNotAllowScrollingWhenOriginOffsetIsOutOfViewport()
123+
{
124+
driver.Url = scrollFrameInViewport;
125+
var scrollOrigin = new WheelInputDevice.ScrollOrigin
126+
{
127+
Viewport = true
128+
};
129+
130+
Assert.That(() => new Actions(driver).Scroll(-10, -10, 0, 200, scrollOrigin).Build().Perform(),
131+
Throws.InstanceOf<MoveTargetOutOfBoundsException>());
132+
}
133+
134+
private bool IsInViewport(IWebElement element)
135+
{
136+
String script =
137+
"for(var e=arguments[0],f=e.offsetTop,t=e.offsetLeft,o=e.offsetWidth,n=e.offsetHeight;\n"
138+
+ "e.offsetParent;)f+=(e=e.offsetParent).offsetTop,t+=e.offsetLeft;\n"
139+
+ "return f<window.pageYOffset+window.innerHeight&&t<window.pageXOffset+window.innerWidth&&f+n>\n"
140+
+ "window.pageYOffset&&t+o>window.pageXOffset";
141+
IJavaScriptExecutor javascriptDriver = this.driver as IJavaScriptExecutor;
142+
143+
return (bool)javascriptDriver.ExecuteScript(script, element);
144+
}
145+
}
146+
}

0 commit comments

Comments
 (0)