Skip to content

Commit 9026176

Browse files
committed
[rb] rework API for scroll methods
1 parent 86aa9c6 commit 9026176

File tree

6 files changed

+193
-103
lines changed

6 files changed

+193
-103
lines changed

‎rb/lib/selenium/webdriver/common.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
require 'selenium/webdriver/common/interactions/pointer_input'
5555
require 'selenium/webdriver/common/interactions/scroll'
5656
require 'selenium/webdriver/common/interactions/wheel_input'
57+
require 'selenium/webdriver/common/interactions/scroll_origin'
5758
require 'selenium/webdriver/common/interactions/wheel_actions'
5859
require 'selenium/webdriver/common/action_builder'
5960
require 'selenium/webdriver/common/html5/shared_web_storage'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
# Licensed to the Software Freedom Conservancy (SFC) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The SFC licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
module Selenium
21+
module WebDriver
22+
module WheelActions
23+
module ScrollOrigin
24+
class << self
25+
def element(element, x = 0, y = 0)
26+
{origin: element, x: x, y: y}
27+
end
28+
29+
def viewport(x, y)
30+
{origin: :viewport, x: x, y: y}
31+
end
32+
end
33+
end
34+
end # WheelActions
35+
end # WebDriver
36+
end # Selenium

‎rb/lib/selenium/webdriver/common/interactions/wheel_actions.rb

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,39 +20,92 @@
2020
module Selenium
2121
module WebDriver
2222
module WheelActions
23+
def default_scroll_duration
24+
@default_scroll_duration ||= 0.25 # 250 milliseconds
25+
end
26+
27+
#
28+
# Scrolls the viewport so that the provided element is at the bottom. Then the viewport
29+
# is further scrolled by the provided x and y offsets.
30+
#
31+
# @example Scroll to element
32+
#
33+
# el = driver.find_element(id: "some_id")
34+
# driver.action.scroll_to(el).perform
35+
#
36+
# @example Scroll to offset from element
37+
#
38+
# el = driver.find_element(id: "some_id")
39+
# driver.action.scroll_to(el, 0, 1000).perform
40+
#
41+
# @param [Selenium::WebDriver::Element] element to scroll to.
42+
# @param [Integer] x Optional horizontal offset to scroll from the center of the element.
43+
# A negative value means scrolling left.
44+
# @param [Integer] y Optional vertical offset to scroll from the center of the element.
45+
# A negative value means scrolling up.
46+
# @param [Symbol || String] device optional name of the WheelInput device to scroll with.
47+
# @return [ActionBuilder] A self reference.
48+
#
2349

24-
# This scrolls an element to the bottom of the viewport
25-
def scroll_to(element, device: nil)
26-
scroll_from(element, device: device)
50+
def scroll_to(element, x = 0, y = 0, device: nil)
51+
scroll(x, y, origin: ScrollOrigin.element(element, 0, 0), device: device)
2752
end
2853

29-
# This scrolls from the provided element
30-
# The origin of the scroll is the center of the element plus the
31-
# offset amounts in origin_offset_x and origin_offset_y
32-
# The amount of scrolling is the value of right_by and down_by
33-
def scroll_from(element, right_by = 0, down_by = 0, origin_offset_x: 0, origin_offset_y: 0, device: nil)
34-
wheel = wheel_input(device)
35-
wheel.create_scroll(x: Integer(origin_offset_x),
36-
y: Integer(origin_offset_y),
37-
delta_x: Integer(right_by),
38-
delta_y: Integer(down_by),
39-
origin: element,
40-
duration: default_move_duration)
41-
tick(wheel)
42-
self
54+
#
55+
# Scrolls the viewport from its current position by the provided offset.
56+
# The origin source is the upper left corner of the viewport
57+
#
58+
# @example Scroll by the provided amount
59+
#
60+
# driver.action.scroll_by(0, 1000).perform
61+
#
62+
# @param [Integer] x horizontal offset. A negative value means scrolling left.
63+
# @param [Integer] y vertical offset. A negative value means scrolling up.
64+
# @param [Symbol || String] device Optional name of the WheelInput device to scroll with
65+
# @return [ActionBuilder] A self reference.
66+
#
67+
68+
def scroll_by(x = 0, y = 0, device: nil)
69+
scroll(x, y, origin: ScrollOrigin.viewport(0, 0), device: device)
4370
end
4471

45-
# The origin of the scroll will the upper left corner of the viewport plus the
46-
# offset amounts in origin_x and origin_y
47-
# The amount of scrolling is the value of right_by and down_by
48-
def scroll_by(right_by = 0, down_by = 0, origin_x: 0, origin_y: 0, device: nil)
72+
#
73+
# Scrolls the viewport based on a ScrollOrigin.
74+
#
75+
# This method is needed instead of #scroll_to or #scroll_by
76+
# when what needs to be scrolled is only in a portion of the viewport.
77+
# The origin can be thought of as where on the screen you put the mouse when
78+
# executing a wheel scroll, or where you put your cursor when swiping a touch pad, etc.
79+
#
80+
# The offset for the origin is referenced to either the upper left of the viewport or the center of the element
81+
# The methods ScrollOrigin.viewport and ScrollOrigin.element are provided to ensure correct syntax
82+
#
83+
# @example Scroll by the provided amount originating from a source offset from upper left of the viewport
84+
#
85+
# el = driver.find_element(id: "some_id")
86+
# driver.action.scroll(0, 100, ScrollOrigin.viewport(400, 200)).perform
87+
#
88+
# @example Scroll by the provided amount originating from a source offset from the center of the provided element
89+
#
90+
# el = driver.find_element(id: "some_id")
91+
# driver.action.scroll(0, 100, ScrollOrigin.element(element, x: -400, 100)).perform
92+
#
93+
# @see ScrollOrigin
94+
#
95+
# @param [Integer] x horizontal offset. A negative value means scrolling left.
96+
# @param [Integer] y vertical offset. A negative value means scrolling up.
97+
# @param [Hash] origin The location the scroll originates from
98+
# @param [Symbol || String] device Optional name of the WheelInput device to scroll with
99+
# @return [ActionBuilder] A self reference.
100+
# @raise [MoveTargetOutOfBoundsError] if the origin value is outside the viewport.
101+
#
102+
103+
def scroll(x, y, origin: ScrollOrigin.viewport(0, 0), device: nil)
49104
wheel = wheel_input(device)
50-
wheel.create_scroll(x: Integer(origin_x),
51-
y: Integer(origin_y),
52-
delta_x: Integer(right_by),
53-
delta_y: Integer(down_by),
54-
origin: Interactions::Scroll::VIEWPORT,
55-
duration: default_move_duration)
105+
opts = {delta_x: Integer(x),
106+
delta_y: Integer(y),
107+
duration: default_scroll_duration}.merge!(origin)
108+
wheel.create_scroll(**opts)
56109
tick(wheel)
57110
self
58111
end
@@ -62,6 +115,6 @@ def scroll_by(right_by = 0, down_by = 0, origin_x: 0, origin_y: 0, device: nil)
62115
def wheel_input(name = nil)
63116
device(name: name, type: Interactions::WHEEL) || add_wheel_input('wheel')
64117
end
65-
end # KeyActions
118+
end # WheelActions
66119
end # WebDriver
67120
end # Selenium

‎rb/spec/integration/selenium/webdriver/action_builder_spec.rb

Lines changed: 47 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ module WebDriver
211211
y_offset = (destination_rect.y - origin_rect.y).ceil
212212

213213
driver.action.move_to(origin, x_offset, y_offset).click.perform
214+
sleep 0.2
214215
expect(destination.attribute(:value)).to eq('Clicked')
215216
end
216217
end
@@ -280,40 +281,17 @@ def in_viewport?(element)
280281

281282
expect(in_viewport?(iframe)).to eq true
282283
end
283-
end
284284

285-
describe '#scroll_from', only: {browser: %i[chrome edge]} do
286-
it 'scrolls from element by the provided amount' do
285+
it 'scrolls to provided offset from element' do
287286
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
288287

289288
iframe = driver.find_element(tag_name: 'iframe')
290-
driver.action.scroll_from(iframe, 0, 200).perform
289+
driver.action.scroll_to(iframe, 0, 200).perform
291290

292291
driver.switch_to.frame(iframe)
293292
checkbox = driver.find_element(name: 'scroll_checkbox')
294293
expect(in_viewport?(checkbox)).to eq true
295294
end
296-
297-
it 'scrolls from element with offset by the provided amount' do
298-
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
299-
300-
footer = driver.find_element(tag_name: 'footer')
301-
driver.action.scroll_from(footer, 0, 200, origin_offset_x: 0, origin_offset_y: -50).perform
302-
303-
driver.switch_to.frame(driver.find_element(tag_name: 'iframe'))
304-
checkbox = driver.find_element(name: 'scroll_checkbox')
305-
expect(in_viewport?(checkbox)).to eq true
306-
end
307-
308-
it 'throws MoveTargetOutOfBoundsError when origin offset is out of viewport' do
309-
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
310-
311-
footer = driver.find_element(tag_name: 'footer')
312-
313-
expect {
314-
driver.action.scroll_from(footer, 0, 200, origin_offset_x: 0, origin_offset_y: 50).perform
315-
}.to raise_error(Error::MoveTargetOutOfBoundsError)
316-
end
317295
end
318296

319297
describe '#scroll_by', only: {browser: %i[chrome edge]} do
@@ -327,24 +305,56 @@ def in_viewport?(element)
327305

328306
expect(in_viewport?(footer)).to eq true
329307
end
308+
end
330309

331-
it 'scrolls by amount provided from provided origin' do
332-
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')
310+
describe '#scroll', only: {browser: %i[chrome edge]} do
311+
context 'when origin is offset from viewport' do
312+
it 'scrolls by amount provided' do
313+
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')
333314

334-
iframe = driver.find_element(tag_name: 'iframe')
335-
driver.action.scroll_by(0, 200, origin_x: 10, origin_y: 10).perform
315+
iframe = driver.find_element(tag_name: 'iframe')
316+
origin = WheelActions::ScrollOrigin.viewport(10, 10)
317+
driver.action.scroll(0, 200, origin: origin).perform
336318

337-
driver.switch_to.frame(iframe)
338-
checkbox = driver.find_element(name: 'scroll_checkbox')
339-
expect(in_viewport?(checkbox)).to eq true
319+
driver.switch_to.frame(iframe)
320+
checkbox = driver.find_element(name: 'scroll_checkbox')
321+
expect(in_viewport?(checkbox)).to eq true
322+
end
323+
324+
it 'raises MoveTargetOutOfBoundsError when origin offset is out of viewport' do
325+
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
326+
327+
origin = WheelActions::ScrollOrigin.viewport(-10, -10)
328+
329+
expect {
330+
driver.action.scroll(0, 200, origin: origin).perform
331+
}.to raise_error(Error::MoveTargetOutOfBoundsError)
332+
end
340333
end
341334

342-
it 'throws MoveTargetOutOfBoundsError when origin offset is out of viewport' do
343-
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame.html')
335+
context 'when origin is offset from center of element' do
336+
it 'scrolls by amount provided' do
337+
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
338+
339+
footer = driver.find_element(tag_name: 'footer')
340+
origin = WheelActions::ScrollOrigin.element(footer, 10, -20)
341+
driver.action.scroll(0, 200, origin: origin).perform
342+
343+
driver.switch_to.frame(driver.find_element(tag_name: 'iframe'))
344+
checkbox = driver.find_element(name: 'scroll_checkbox')
345+
expect(in_viewport?(checkbox)).to eq true
346+
end
344347

345-
expect {
346-
driver.action.scroll_by(0, 200, origin_x: -10, origin_y: -10).perform
347-
}.to raise_error(Error::MoveTargetOutOfBoundsError)
348+
it 'raises MoveTargetOutOfBoundsError when origin offset is out of viewport' do
349+
driver.navigate.to url_for('scrolling_tests/frame_with_nested_scrolling_frame_out_of_view.html')
350+
351+
footer = driver.find_element(tag_name: 'footer')
352+
origin = WheelActions::ScrollOrigin.element(footer, 10, 20)
353+
354+
expect {
355+
driver.action.scroll(0, 200, origin: origin).perform
356+
}.to raise_error(Error::MoveTargetOutOfBoundsError)
357+
end
348358
end
349359
end
350360
end # ActionBuilder

‎rb/spec/unit/selenium/webdriver/common/interactions/pointer_input_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ module Interactions
8585

8686
describe '#create_pointer_up' do
8787
it 'executes #add_action with created interaction' do
88-
allow(PointerPress).to receive(:new).with(pointer, :up, :left, {}).and_return(interaction)
88+
allow(PointerPress).to receive(:new).and_return(interaction)
8989
allow(pointer).to receive(:add_action).and_call_original
9090

9191
pointer.create_pointer_up(:left)
9292

93+
expect(PointerPress).to have_received(:new).with(pointer, :up, :left, {})
9394
expect(pointer).to have_received(:add_action).with(interaction)
9495
end
9596
end

0 commit comments

Comments
 (0)