mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Merge pull request #1803 from teamcapybara/switch_to_frame
Implement Session#switch_to_frame API
This commit is contained in:
commit
ad439ba37f
4 changed files with 176 additions and 26 deletions
14
History.md
14
History.md
|
@ -1,4 +1,10 @@
|
||||||
#2.11.0
|
#Edge
|
||||||
|
Release date: unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Session#switch_to_frame for manually handling frame switching - Issue #1365 [Thomas Walpole]
|
||||||
|
|
||||||
|
#Version 2.11.0
|
||||||
Release date: 2016-12-05
|
Release date: 2016-12-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -13,14 +19,14 @@ Release date: 2016-12-05
|
||||||
* Selenium driver with Chrome should support multiple file upload [Thomas Walpole]
|
* Selenium driver with Chrome should support multiple file upload [Thomas Walpole]
|
||||||
* Fix visible: :hidden with :text option behavior [Thomas Walpole]
|
* Fix visible: :hidden with :text option behavior [Thomas Walpole]
|
||||||
|
|
||||||
#2.10.2
|
#Version 2.10.2
|
||||||
Release date: 2016-11-30
|
Release date: 2016-11-30
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* App exceptions with multiple parameter initializers now re-raised correctly - Issue #1785 [Michael Lutsiuk]
|
* App exceptions with multiple parameter initializers now re-raised correctly - Issue #1785 [Michael Lutsiuk]
|
||||||
* Use Addressable::URI when parsing current_path since it's more lenient of technically invalid URLs - Issue #1801 [Marcos Duque, Thomas Walpole]
|
* Use Addressable::URI when parsing current_path since it's more lenient of technically invalid URLs - Issue #1801 [Marcos Duque, Thomas Walpole]
|
||||||
|
|
||||||
#2.10.1
|
#Version 2.10.1
|
||||||
Release date: 2016-10-08
|
Release date: 2016-10-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -28,7 +34,7 @@ Release date: 2016-10-08
|
||||||
* Capybara::Result optimization disabled in JRuby due to issue with lazy enumerator evaluation [Thomas Walpole]
|
* Capybara::Result optimization disabled in JRuby due to issue with lazy enumerator evaluation [Thomas Walpole]
|
||||||
See: https://github.com/jruby/jruby/issues/4212
|
See: https://github.com/jruby/jruby/issues/4212
|
||||||
|
|
||||||
#2.10.0
|
#Version 2.10.0
|
||||||
Release date: 2016-10-05
|
Release date: 2016-10-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -50,7 +50,7 @@ module Capybara
|
||||||
SESSION_METHODS = [
|
SESSION_METHODS = [
|
||||||
:body, :html, :source, :current_url, :current_host, :current_path,
|
:body, :html, :source, :current_url, :current_host, :current_path,
|
||||||
:execute_script, :evaluate_script, :visit, :go_back, :go_forward,
|
:execute_script, :evaluate_script, :visit, :go_back, :go_forward,
|
||||||
:within, :within_fieldset, :within_table, :within_frame, :current_window,
|
:within, :within_fieldset, :within_table, :switch_to_frame, :within_frame, :current_window,
|
||||||
:windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
|
:windows, :open_new_window, :switch_to_window, :within_window, :window_opened_by,
|
||||||
:save_page, :save_and_open_page, :save_screenshot,
|
:save_page, :save_and_open_page, :save_screenshot,
|
||||||
:save_and_open_screenshot, :reset_session!, :response_headers,
|
:save_and_open_screenshot, :reset_session!, :response_headers,
|
||||||
|
@ -331,6 +331,42 @@ module Capybara
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
#
|
||||||
|
# Switch to the given frame
|
||||||
|
#
|
||||||
|
# If you use this method you are responsible for making sure you switch back to the parent frame when done in the frame changed to.
|
||||||
|
# Capybara::Session#within_frame is preferred over this method and should be used when possible.
|
||||||
|
# May not be supported by all drivers.
|
||||||
|
#
|
||||||
|
# @overload switch_to_frame(element)
|
||||||
|
# @param [Capybara::Node::Element] iframe/frame element to switch to
|
||||||
|
# @overload switch_to_frame(:parent)
|
||||||
|
# Switch to the parent element
|
||||||
|
# @overload switch_to_frame(:top)
|
||||||
|
# Switch to the top level document
|
||||||
|
#
|
||||||
|
def switch_to_frame(frame)
|
||||||
|
case frame
|
||||||
|
when Capybara::Node::Element
|
||||||
|
driver.switch_to_frame(frame)
|
||||||
|
scopes.push(:frame)
|
||||||
|
when :parent
|
||||||
|
raise Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's "\
|
||||||
|
"`within` block." if scopes.last() != :frame
|
||||||
|
scopes.pop
|
||||||
|
driver.switch_to_frame(:parent)
|
||||||
|
when :top
|
||||||
|
idx = scopes.index(:frame)
|
||||||
|
if idx
|
||||||
|
raise Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's "\
|
||||||
|
"`within` block." if scopes.slice(idx..-1).any? {|scope| ![:frame, nil].include?(scope)}
|
||||||
|
scopes.slice!(idx..-1)
|
||||||
|
driver.switch_to_frame(:top)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
#
|
#
|
||||||
# Execute the given block within the given iframe using given frame, frame name/id or index.
|
# Execute the given block within the given iframe using given frame, frame name/id or index.
|
||||||
|
@ -344,41 +380,44 @@ module Capybara
|
||||||
# @overload within_frame(index)
|
# @overload within_frame(index)
|
||||||
# @param [Integer] index index of a frame (0 based)
|
# @param [Integer] index index of a frame (0 based)
|
||||||
def within_frame(*args)
|
def within_frame(*args)
|
||||||
scopes.push(nil)
|
frame = within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
|
||||||
|
case args[0]
|
||||||
frame = case args[0]
|
when Capybara::Node::Element
|
||||||
when Capybara::Node::Element
|
args[0]
|
||||||
args[0]
|
when String, Hash
|
||||||
when String, Hash
|
find(:frame, *args)
|
||||||
find(:frame, *args)
|
when Symbol
|
||||||
when Symbol
|
find(*args)
|
||||||
find(*args)
|
when Integer
|
||||||
when Integer
|
idx = args[0]
|
||||||
idx = args[0]
|
all(:frame, minimum: idx+1)[idx]
|
||||||
all(:frame, minimum: idx+1)[idx]
|
else
|
||||||
else
|
raise ArgumentError
|
||||||
raise ArgumentError
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
begin
|
||||||
driver.switch_to_frame(frame)
|
switch_to_frame(frame)
|
||||||
begin
|
begin
|
||||||
yield
|
yield
|
||||||
ensure
|
ensure
|
||||||
driver.switch_to_frame(:parent)
|
switch_to_frame(:parent)
|
||||||
end
|
end
|
||||||
rescue Capybara::NotSupportedByDriverError
|
rescue Capybara::NotSupportedByDriverError
|
||||||
# Support older driver frame API for now
|
# Support older driver frame API for now
|
||||||
if driver.respond_to?(:within_frame)
|
if driver.respond_to?(:within_frame)
|
||||||
driver.within_frame(frame) do
|
begin
|
||||||
yield
|
scopes.push(:frame)
|
||||||
|
driver.within_frame(frame) do
|
||||||
|
yield
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
scopes.pop
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ensure
|
|
||||||
scopes.pop
|
|
||||||
end
|
end
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -735,7 +774,9 @@ module Capybara
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_scope
|
def current_scope
|
||||||
scopes.last || document
|
scope = scopes.last
|
||||||
|
scope = document if [nil, :frame].include? scope
|
||||||
|
scope
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
103
lib/capybara/spec/session/frame/switch_to_frame_spec.rb
Normal file
103
lib/capybara/spec/session/frame/switch_to_frame_spec.rb
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
Capybara::SpecHelper.spec '#switch_to_frame', requires: [:frames] do
|
||||||
|
before(:each) do
|
||||||
|
@session.visit('/within_frames')
|
||||||
|
end
|
||||||
|
|
||||||
|
after(:each) do
|
||||||
|
# Ensure we clean up after the frame changes
|
||||||
|
@session.switch_to_frame(:top)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should find the div in frameOne" do
|
||||||
|
frame = @session.find(:frame, "frameOne")
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
expect(@session.find("//*[@id='divInFrameOne']").text).to eql 'This is the text of divInFrameOne'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should find the div in FrameTwo" do
|
||||||
|
frame = @session.find(:frame, "frameTwo")
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
expect(@session.find("//*[@id='divInFrameTwo']").text).to eql 'This is the text of divInFrameTwo'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return to the parent frame when told to" do
|
||||||
|
frame = @session.find(:frame, "frameOne")
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
@session.switch_to_frame(:parent)
|
||||||
|
expect(@session.find("//*[@id='divInMainWindow']").text).to eql 'This is the text for divInMainWindow'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to switch to nested frames" do
|
||||||
|
frame = @session.find(:frame, "parentFrame")
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
frame = @session.find(:frame, "childFrame")
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
frame = @session.find(:frame, "grandchildFrame1")
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
expect(@session).to have_selector(:css, '#divInFrameOne', text: 'This is the text of divInFrameOne')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should reset scope when changing frames" do
|
||||||
|
frame = @session.find(:frame, 'parentFrame')
|
||||||
|
@session.within(:css, '#divInMainWindow') do
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
expect(@session.has_selector?(:css, "iframe#childFrame")).to be true
|
||||||
|
@session.switch_to_frame(:parent)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "works if the frame is closed", requires: [:frames, :js] do
|
||||||
|
frame = @session.find(:frame, 'parentFrame')
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
frame = @session.find(:frame, 'childFrame')
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
|
||||||
|
@session.click_link 'Close Window'
|
||||||
|
@session.switch_to_frame :parent # Go back to parentFrame
|
||||||
|
expect(@session).to have_selector(:css, 'body#parentBody')
|
||||||
|
expect(@session).not_to have_selector(:css, '#childFrame')
|
||||||
|
@session.switch_to_frame :parent # Go back to top
|
||||||
|
end
|
||||||
|
|
||||||
|
it "can return to the top frame", requires: [:frames] do
|
||||||
|
frame = @session.find(:frame, "parentFrame")
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
frame = @session.find(:frame, "childFrame")
|
||||||
|
@session.switch_to_frame frame
|
||||||
|
@session.switch_to_frame :top
|
||||||
|
expect(@session.find("//*[@id='divInMainWindow']").text).to eql 'This is the text for divInMainWindow'
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should raise error if switching to parent unmatched inside `within` as it's nonsense" do
|
||||||
|
expect do
|
||||||
|
frame = @session.find(:frame, 'parentFrame')
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
@session.within(:css, '#parentBody') do
|
||||||
|
@session.switch_to_frame(:parent)
|
||||||
|
end
|
||||||
|
end.to raise_error(Capybara::ScopeError, "`switch_to_frame(:parent)` cannot be called from inside a descendant frame's `within` block.")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should raise error if switching to top inside a `within` in a frame as it's nonsense" do
|
||||||
|
frame = @session.find(:frame, 'parentFrame')
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
@session.within(:css, '#parentBody') do
|
||||||
|
expect do
|
||||||
|
@session.switch_to_frame(:top)
|
||||||
|
end.to raise_error(Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's `within` block.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should raise error if switching to top inside a nested `within` in a frame as it's nonsense" do
|
||||||
|
frame = @session.find(:frame, 'parentFrame')
|
||||||
|
@session.switch_to_frame(frame)
|
||||||
|
@session.within(:css, '#parentBody') do
|
||||||
|
@session.switch_to_frame(@session.find(:frame, "childFrame"))
|
||||||
|
expect do
|
||||||
|
@session.switch_to_frame(:top)
|
||||||
|
end.to raise_error(Capybara::ScopeError, "`switch_to_frame(:top)` cannot be called from inside a descendant frame's `within` block.")
|
||||||
|
@session.switch_to_frame(:parent)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue