Add Session#scroll_to/scroll_by and Element#scroll_to/scroll_by
This commit is contained in:
parent
6105d95051
commit
f30472fd4c
|
@ -68,6 +68,14 @@ module Capybara
|
|||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def scroll_by(x, y)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def scroll_to(element, location = :top)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def tag_name
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
|
|
@ -31,6 +31,22 @@ module Capybara
|
|||
def title
|
||||
session.driver.title
|
||||
end
|
||||
|
||||
def execute_script(*args)
|
||||
find(:xpath, '/html').execute_script(*args)
|
||||
end
|
||||
|
||||
def evaluate_script(*args)
|
||||
find(:xpath, '/html').evaluate_script(*args)
|
||||
end
|
||||
|
||||
def scroll_to(*args)
|
||||
find(:xpath, '//body').scroll_to(*args)
|
||||
end
|
||||
|
||||
def scroll_by(*args)
|
||||
find(:xpath, '//body').scroll_by(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -381,6 +381,17 @@ module Capybara
|
|||
self
|
||||
end
|
||||
|
||||
def scroll_to(pos_or_el, position = :top)
|
||||
pos_or_el = pos_or_el.base if pos_or_el.is_a? Capybara::Node::Element
|
||||
synchronize { base.scroll_to(pos_or_el, position) }
|
||||
self
|
||||
end
|
||||
|
||||
def scroll_by(x, y)
|
||||
synchronize { base.scroll_by(x, y) }
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Capybara
|
||||
module Selenium
|
||||
module Scroll
|
||||
def scroll_by(x, y)
|
||||
driver.execute_script <<~JS, self, x, y
|
||||
var el = arguments[0];
|
||||
if (el.scrollBy){
|
||||
el.scrollBy(arguments[1], arguments[2]);
|
||||
} else {
|
||||
el.scrollTop = el.scrollTop + arguments[2];
|
||||
el.scrollLeft = el.scrollLeft + arguments[1];
|
||||
}
|
||||
JS
|
||||
end
|
||||
|
||||
def scroll_to(element, location = :top)
|
||||
location, element = element, nil if element.is_a? Symbol
|
||||
|
||||
if element.is_a? Capybara::Selenium::Node
|
||||
scroll_element_to_location(element, location)
|
||||
elsif location.is_a? Symbol
|
||||
scroll_to_location(location)
|
||||
else
|
||||
x, y = element, location
|
||||
scroll_to_coords(x, y)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scroll_element_to_location(element, location)
|
||||
scroll_opts = case location
|
||||
when :top
|
||||
'true'
|
||||
when :bottom
|
||||
'false'
|
||||
when :center
|
||||
"{behavior: 'instant', block: 'center'}"
|
||||
else
|
||||
raise ArgumentError, "Invalid scroll_to location: #{location}"
|
||||
end
|
||||
driver.execute_script <<~JS, element
|
||||
arguments[0].scrollIntoView(#{scroll_opts})
|
||||
JS
|
||||
end
|
||||
|
||||
def scroll_to_location(location)
|
||||
scroll_y = case location
|
||||
when :top
|
||||
'0'
|
||||
when :bottom
|
||||
'arguments[0].scrollHeight'
|
||||
when :center
|
||||
'(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
|
||||
end
|
||||
driver.execute_script <<~JS, self
|
||||
if (arguments[0].scrollTo){
|
||||
arguments[0].scrollTo(0, #{scroll_y});
|
||||
} else {
|
||||
arguments[0].scrollTop = #{scroll_y};
|
||||
}
|
||||
JS
|
||||
end
|
||||
|
||||
def scroll_to_coords(x, y)
|
||||
driver.execute_script <<~JS, self, x, y
|
||||
if (this.scrollTo){
|
||||
arguments[0].scrollTo(arguments[1], arguments[2]);
|
||||
} else {
|
||||
arguments[0].scrollTop = arguments[2];
|
||||
arguments[0].scrollLeft = arguments[1];
|
||||
}
|
||||
JS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# Selenium specific implementation of the Capybara::Driver::Node API
|
||||
|
||||
require 'capybara/selenium/extensions/scroll'
|
||||
|
||||
class Capybara::Selenium::Node < Capybara::Driver::Node
|
||||
include Capybara::Selenium::Scroll
|
||||
|
||||
def visible_text
|
||||
native.text
|
||||
end
|
||||
|
|
|
@ -39,7 +39,7 @@ module Capybara
|
|||
include Capybara::SessionMatchers
|
||||
|
||||
NODE_METHODS = %i[
|
||||
all first attach_file text check choose
|
||||
all first attach_file text check choose scroll_to scroll_by
|
||||
click_link_or_button click_button click_link
|
||||
fill_in find find_all find_button find_by_id find_field find_link
|
||||
has_content? has_text? has_css? has_no_content? has_no_text?
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
|
||||
before do
|
||||
@session.visit('/scroll')
|
||||
end
|
||||
|
||||
it 'can scroll an element to the top of the viewport' do
|
||||
el = @session.find(:css, '#scroll')
|
||||
@session.scroll_to(el, :top)
|
||||
expect(el.evaluate_script('this.getBoundingClientRect().top')).to be_within(1).of(0)
|
||||
end
|
||||
|
||||
it 'can scroll an element to the bottom of the viewport' do
|
||||
el = @session.find(:css, '#scroll')
|
||||
@session.scroll_to(el, :bottom)
|
||||
el_bottom = el.evaluate_script('this.getBoundingClientRect().bottom')
|
||||
viewport_bottom = el.evaluate_script('document.body.clientHeight')
|
||||
expect(el_bottom).to be_within(1).of(viewport_bottom)
|
||||
end
|
||||
|
||||
it 'can scroll an element to the center of the viewport' do
|
||||
el = @session.find(:css, '#scroll')
|
||||
@session.scroll_to(el, :center)
|
||||
el_center = el.evaluate_script('(function(rect){return (rect.top + rect.bottom)/2})(this.getBoundingClientRect())')
|
||||
viewport_bottom = el.evaluate_script('document.body.clientHeight')
|
||||
expect(el_center).to be_within(1).of(viewport_bottom / 2)
|
||||
end
|
||||
|
||||
it 'can scroll the window to the vertical top' do
|
||||
@session.scroll_to :bottom
|
||||
@session.scroll_to :top
|
||||
expect(@session.evaluate_script('[window.scrollX, window.scrollY]')).to eq [0, 0]
|
||||
end
|
||||
|
||||
it 'can scroll the window to the vertical bottom' do
|
||||
@session.scroll_to :bottom
|
||||
max_scroll = @session.evaluate_script('document.body.scrollHeight - document.body.clientHeight')
|
||||
expect(@session.evaluate_script('[window.scrollX, window.scrollY]')).to eq [0, max_scroll]
|
||||
end
|
||||
|
||||
it 'can scroll the window to the vertical center' do
|
||||
@session.scroll_to :center
|
||||
max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.body.clientHeight')
|
||||
expect(@session.evaluate_script('[window.scrollX, window.scrollY]')).to eq [0, max_scroll / 2]
|
||||
end
|
||||
|
||||
it 'can scroll the window to specifc location' do
|
||||
@session.scroll_to 100, 100
|
||||
expect(@session.evaluate_script('[window.scrollX, window.scrollY]')).to eq [100, 100]
|
||||
end
|
||||
|
||||
it 'can scroll an element to the top of the scrolling element' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
el = scrolling_element.find(:css, '#inner')
|
||||
scrolling_element.scroll_to(el, :top)
|
||||
scrolling_element_top = scrolling_element.evaluate_script('this.getBoundingClientRect().top')
|
||||
expect(el.evaluate_script('this.getBoundingClientRect().top')).to be_within(1).of(scrolling_element_top)
|
||||
end
|
||||
|
||||
it 'can scroll an element to the bottom of the scrolling element' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
el = scrolling_element.find(:css, '#inner')
|
||||
scrolling_element.scroll_to(el, :bottom)
|
||||
el_bottom = el.evaluate_script('this.getBoundingClientRect().bottom')
|
||||
scroller_bottom = scrolling_element.evaluate_script('this.getBoundingClientRect().top + this.clientHeight')
|
||||
expect(el_bottom).to be_within(1).of(scroller_bottom)
|
||||
end
|
||||
|
||||
it 'can scroll an element to the center of the scrolling element' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
el = scrolling_element.find(:css, '#inner')
|
||||
scrolling_element.scroll_to(el, :center)
|
||||
el_center = el.evaluate_script('(function(rect){return (rect.top + rect.bottom)/2})(this.getBoundingClientRect())')
|
||||
scrollable_center = scrolling_element.evaluate_script('(this.clientHeight / 2) + this.getBoundingClientRect().top')
|
||||
expect(el_center).to be_within(1).of(scrollable_center)
|
||||
end
|
||||
|
||||
it 'can scroll the scrolling element to the top' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
scrolling_element.scroll_to :bottom
|
||||
scrolling_element.scroll_to :top
|
||||
expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [0, 0]
|
||||
end
|
||||
|
||||
it 'can scroll the scrolling element to the bottom' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
scrolling_element.scroll_to :bottom
|
||||
max_scroll = scrolling_element.evaluate_script('this.scrollHeight - this.clientHeight')
|
||||
expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [0, max_scroll]
|
||||
end
|
||||
|
||||
it 'can scroll the scrolling element to the vertical center' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
scrolling_element.scroll_to :center
|
||||
max_scroll = scrolling_element.evaluate_script('this.scrollHeight - this.clientHeight')
|
||||
expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [0, max_scroll / 2]
|
||||
end
|
||||
|
||||
it 'can scroll the scrolling element to specifc location' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
scrolling_element.scroll_to 100, 100
|
||||
expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [100, 100]
|
||||
end
|
||||
|
||||
context 'scroll_by' do
|
||||
it 'can scroll the window by a specifc amount' do
|
||||
@session.scroll_by 100, 100
|
||||
expect(@session.evaluate_script('[window.scrollX, window.scrollY]')).to eq [100, 100]
|
||||
end
|
||||
|
||||
it 'can scroll the scroll the scrolling element by a specifc amount' do
|
||||
scrolling_element = @session.find(:css, '#scrollable')
|
||||
scrolling_element.scroll_to 100, 100
|
||||
scrolling_element.scroll_by(-50, 50)
|
||||
expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [50, 150]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
<html style="height: 250%; width: 150%">
|
||||
<head>
|
||||
<style>
|
||||
* { padding: 0; }
|
||||
div { position: absolute; }
|
||||
#scroll { top: 125%; }
|
||||
#scrollable { top: 150%; overflow-y: scroll; overflow-x: scroll; height: 50px; width: 50px; position: relative; border: 1px solid black;}
|
||||
#inner { top: 50%; }
|
||||
</style>
|
||||
</head>
|
||||
<body style="height: 100%; width: 100%">
|
||||
<div id="scroll" style = "top: 125%">scroll</div>
|
||||
<div id="scrollable">
|
||||
<div style="height: 200px; width: 200px; poistion: relative">
|
||||
<div id="inner">inner</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -8,7 +8,7 @@ class TestClass
|
|||
end
|
||||
|
||||
Capybara::SpecHelper.run_specs TestClass.new, 'DSL', capybara_skip: %i[
|
||||
js modals screenshot frames windows send_keys server hover about_scheme psc download css driver
|
||||
js modals screenshot frames windows send_keys server hover about_scheme psc download css driver scroll
|
||||
] do |example|
|
||||
case example.metadata[:full_description]
|
||||
when /has_css\? should support case insensitive :class and :id options/
|
||||
|
|
|
@ -24,6 +24,7 @@ skipped_tests = %i[
|
|||
about_scheme
|
||||
download
|
||||
css
|
||||
scroll
|
||||
]
|
||||
Capybara::SpecHelper.run_specs TestSessions::RackTest, 'RackTest', capybara_skip: skipped_tests do |example|
|
||||
case example.metadata[:full_description]
|
||||
|
|
|
@ -51,6 +51,6 @@ end
|
|||
|
||||
RSpec.configure do |config|
|
||||
Capybara::SpecHelper.configure(config)
|
||||
config.filter_run_including focus_: true unless ENV['CI']
|
||||
config.filter_run_including focus_: true # unless ENV['CI']
|
||||
config.run_all_when_everything_filtered = true
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue