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
|
||||
|
||||
### Added
|
||||
|
@ -13,14 +19,14 @@ Release date: 2016-12-05
|
|||
* Selenium driver with Chrome should support multiple file upload [Thomas Walpole]
|
||||
* Fix visible: :hidden with :text option behavior [Thomas Walpole]
|
||||
|
||||
#2.10.2
|
||||
#Version 2.10.2
|
||||
Release date: 2016-11-30
|
||||
|
||||
### Fixed
|
||||
* 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]
|
||||
|
||||
#2.10.1
|
||||
#Version 2.10.1
|
||||
Release date: 2016-10-08
|
||||
|
||||
### 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]
|
||||
See: https://github.com/jruby/jruby/issues/4212
|
||||
|
||||
#2.10.0
|
||||
#Version 2.10.0
|
||||
Release date: 2016-10-05
|
||||
|
||||
### Added
|
||||
|
|
|
@ -50,7 +50,7 @@ module Capybara
|
|||
SESSION_METHODS = [
|
||||
:body, :html, :source, :current_url, :current_host, :current_path,
|
||||
: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,
|
||||
:save_page, :save_and_open_page, :save_screenshot,
|
||||
:save_and_open_screenshot, :reset_session!, :response_headers,
|
||||
|
@ -331,6 +331,42 @@ module Capybara
|
|||
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.
|
||||
|
@ -344,9 +380,8 @@ module Capybara
|
|||
# @overload within_frame(index)
|
||||
# @param [Integer] index index of a frame (0 based)
|
||||
def within_frame(*args)
|
||||
scopes.push(nil)
|
||||
|
||||
frame = case args[0]
|
||||
frame = within(document) do # Previous 2.x versions ignored current scope when finding frames - consider changing in 3.0
|
||||
case args[0]
|
||||
when Capybara::Node::Element
|
||||
args[0]
|
||||
when String, Hash
|
||||
|
@ -359,26 +394,30 @@ module Capybara
|
|||
else
|
||||
raise ArgumentError
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
driver.switch_to_frame(frame)
|
||||
switch_to_frame(frame)
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
driver.switch_to_frame(:parent)
|
||||
switch_to_frame(:parent)
|
||||
end
|
||||
rescue Capybara::NotSupportedByDriverError
|
||||
# Support older driver frame API for now
|
||||
if driver.respond_to?(:within_frame)
|
||||
begin
|
||||
scopes.push(:frame)
|
||||
driver.within_frame(frame) do
|
||||
yield
|
||||
end
|
||||
ensure
|
||||
scopes.pop
|
||||
end
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
ensure
|
||||
scopes.pop
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -735,7 +774,9 @@ module Capybara
|
|||
end
|
||||
|
||||
def current_scope
|
||||
scopes.last || document
|
||||
scope = scopes.last
|
||||
scope = document if [nil, :frame].include? scope
|
||||
scope
|
||||
end
|
||||
|
||||
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