2016-03-07 19:52:19 -05:00
# frozen_string_literal: true
2015-08-24 01:14:48 -04:00
require 'capybara/session/matchers'
2016-11-30 12:03:29 -05:00
require 'addressable/uri'
2015-08-24 01:14:48 -04:00
2009-11-26 17:47:58 -05:00
module Capybara
2010-01-01 11:48:39 -05:00
2010-07-11 11:26:08 -04:00
##
#
2010-07-14 17:59:23 -04:00
# The Session class represents a single user's interaction with the system. The Session can use
# any of the underlying drivers. A session can be initialized manually like this:
2010-07-11 11:26:08 -04:00
#
# session = Capybara::Session.new(:culerity, MyRackApp)
#
# The application given as the second argument is optional. When running Capybara against an external
# page, you might want to leave it out:
#
# session = Capybara::Session.new(:culerity)
# session.visit('http://www.google.com')
#
# Session provides a number of methods for controlling the navigation of the page, such as +visit+,
# +current_path, and so on. It also delegate a number of methods to a Capybara::Document, representing
# the current HTML document. This allows interaction:
#
2016-10-04 14:10:29 -04:00
# session.fill_in('q', with: 'Capybara')
2010-07-11 11:26:08 -04:00
# session.click_button('Search')
2013-11-14 12:43:36 -05:00
# expect(session).to have_content('Capybara')
2010-07-11 11:26:08 -04:00
#
# When using capybara/dsl, the Session is initialized automatically for you.
#
class Session
2015-08-24 01:14:48 -04:00
include Capybara :: SessionMatchers
2011-03-25 09:22:47 -04:00
NODE_METHODS = [
2011-03-31 11:57:24 -04:00
:all , :first , :attach_file , :text , :check , :choose ,
:click_link_or_button , :click_button , :click_link , :field_labeled ,
2015-04-23 14:11:57 -04:00
:fill_in , :find , :find_all , :find_button , :find_by_id , :find_field , :find_link ,
2011-11-15 10:58:05 -05:00
:has_content? , :has_text? , :has_css? , :has_no_content? , :has_no_text? ,
2012-07-21 16:44:10 -04:00
:has_no_css? , :has_no_xpath? , :resolve , :has_xpath? , :select , :uncheck ,
:has_link? , :has_no_link? , :has_button? , :has_no_button? , :has_field? ,
:has_no_field? , :has_checked_field? , :has_unchecked_field? ,
:has_no_table? , :has_table? , :unselect , :has_select? , :has_no_select? ,
:has_selector? , :has_no_selector? , :click_on , :has_no_checked_field? ,
2014-01-29 06:45:18 -05:00
:has_no_unchecked_field? , :query , :assert_selector , :assert_no_selector ,
2017-01-02 15:07:02 -05:00
:assert_all_of_selectors , :assert_none_of_selectors ,
2014-02-16 12:13:58 -05:00
:refute_selector , :assert_text , :assert_no_text
]
# @api private
DOCUMENT_METHODS = [
:title , :assert_title , :assert_no_title , :has_title? , :has_no_title?
2009-12-29 22:05:38 -05:00
]
2011-03-25 09:22:47 -04:00
SESSION_METHODS = [
2013-06-20 16:40:32 -04:00
:body , :html , :source , :current_url , :current_host , :current_path ,
2013-10-05 14:23:51 -04:00
:execute_script , :evaluate_script , :visit , :go_back , :go_forward ,
2017-01-18 11:47:48 -05:00
:within , :within_element , :within_fieldset , :within_table , :within_frame , :switch_to_frame ,
:current_window , :windows , :open_new_window , :switch_to_window , :within_window , :window_opened_by ,
2013-06-20 16:40:32 -04:00
:save_page , :save_and_open_page , :save_screenshot ,
2014-03-21 22:09:15 -04:00
:save_and_open_screenshot , :reset_session! , :response_headers ,
2015-08-24 01:14:48 -04:00
:status_code , :current_scope ,
:assert_current_path , :assert_no_current_path , :has_current_path? , :has_no_current_path?
2014-02-16 12:13:58 -05:00
] + DOCUMENT_METHODS
2013-04-01 18:41:55 -04:00
MODAL_METHODS = [
:accept_alert , :accept_confirm , :dismiss_confirm , :accept_prompt ,
2014-06-27 15:37:33 -04:00
:dismiss_prompt
2013-04-01 18:41:55 -04:00
]
DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
2010-01-11 17:14:52 -05:00
2012-07-13 08:57:43 -04:00
attr_reader :mode , :app , :server
2013-03-11 19:11:05 -04:00
attr_accessor :synchronized
2009-11-09 17:51:39 -05:00
2010-03-12 13:07:15 -05:00
def initialize ( mode , app = nil )
2016-12-28 19:49:45 -05:00
raise TypeError , " The second parameter to Session::new should be a rack app if passed. " if app && ! app . respond_to? ( :call )
2009-12-18 11:40:51 -05:00
@mode = mode
2009-11-26 17:47:58 -05:00
@app = app
2012-07-13 09:19:20 -04:00
if Capybara . run_server and @app and driver . needs_server?
@server = Capybara :: Server . new ( @app ) . boot
2012-12-02 01:14:42 -05:00
else
@server = nil
2012-07-13 07:29:02 -04:00
end
2012-12-02 01:30:29 -05:00
@touched = false
2009-11-16 14:13:06 -05:00
end
2010-01-01 11:48:39 -05:00
2009-11-26 17:47:58 -05:00
def driver
2010-07-29 09:20:11 -04:00
@driver || = begin
unless Capybara . drivers . has_key? ( mode )
other_drivers = Capybara . drivers . keys . map { | key | key . inspect }
raise Capybara :: DriverNotFoundError , " no driver called #{ mode . inspect } was found, available drivers: #{ other_drivers . join ( ', ' ) } "
end
Capybara . drivers [ mode ] . call ( app )
2009-11-26 17:47:58 -05:00
end
2009-11-15 07:40:50 -05:00
end
2009-11-26 17:47:58 -05:00
2010-07-11 11:26:08 -04:00
##
#
2014-04-03 03:57:31 -04:00
# Reset the session (i.e. remove cookies and navigate to blank page)
#
# This method does not:
#
2014-07-01 03:24:12 -04:00
# * accept modal dialogs if they are present (Selenium driver now does, others may not)
2014-04-03 03:57:31 -04:00
# * clear browser cache/HTML 5 local storage/IndexedDB/Web SQL database/etc.
# * modify state of the driver/underlying browser in any other way
#
# as doing so will result in performance downsides and it's not needed to do everything from the list above for most apps.
#
# If you want to do anything from the list above on a general basis you can:
#
# * write RSpec/Cucumber/etc. after hook
# * monkeypatch this method
# * use Ruby's `prepend` method
2010-07-11 11:26:08 -04:00
#
2010-07-29 09:25:45 -04:00
def reset!
2013-10-20 13:29:22 -04:00
if @touched
driver . reset!
2014-01-17 02:57:06 -05:00
@touched = false
2013-10-20 13:29:22 -04:00
end
2015-07-06 19:19:47 -04:00
@server . wait_for_pending_requests if @server
2014-03-02 09:49:00 -05:00
raise_server_error!
end
alias_method :cleanup! , :reset!
alias_method :reset_session! , :reset!
##
#
# Raise errors encountered in the server
#
def raise_server_error!
2016-07-13 14:11:11 -04:00
if Capybara . raise_server_errors and @server and @server . error
# Force an explanation for the error being raised as the exception cause
begin
raise CapybaraError , " Your application server raised an error - It has been raised in your test code because Capybara.raise_server_errors == true "
rescue CapybaraError
2016-11-29 11:12:37 -05:00
#needed to get the cause set correctly in JRuby -- otherwise we could just do raise @server.error
2016-11-07 12:34:57 -05:00
raise @server . error , @server . error . message , @server . error . backtrace
2016-07-13 14:11:11 -04:00
end
end
2012-07-13 08:57:43 -04:00
ensure
@server . reset_error! if @server
2010-07-11 08:00:00 -04:00
end
2009-11-26 17:47:58 -05:00
2010-07-11 11:26:08 -04:00
##
#
# Returns a hash of response headers. Not supported by all drivers (e.g. Selenium)
#
2010-07-14 18:24:59 -04:00
# @return [Hash{String => String}] A hash of response headers.
2010-07-11 11:26:08 -04:00
#
2010-07-11 08:00:00 -04:00
def response_headers
driver . response_headers
2009-12-13 09:03:19 -05:00
end
2010-07-11 11:26:08 -04:00
##
#
# Returns the current HTTP status code as an Integer. Not supported by all drivers (e.g. Selenium)
#
2010-07-14 18:24:59 -04:00
# @return [Integer] Current HTTP status code
2010-07-11 11:26:08 -04:00
#
2010-07-11 08:00:00 -04:00
def status_code
driver . status_code
2009-11-24 15:45:52 -05:00
end
2009-11-07 12:56:04 -05:00
2010-07-11 11:26:08 -04:00
##
#
2012-09-09 21:05:17 -04:00
# @return [String] A snapshot of the DOM of the current document, as it looks right now (potentially modified by JavaScript).
2010-07-11 11:26:08 -04:00
#
2011-11-15 10:58:05 -05:00
def html
2012-09-09 21:05:17 -04:00
driver . html
2010-07-11 08:00:00 -04:00
end
2012-11-20 08:22:19 -05:00
alias_method :body , :html
alias_method :source , :html
2010-07-11 08:00:00 -04:00
2010-07-11 11:26:08 -04:00
##
#
2010-07-14 18:24:59 -04:00
# @return [String] Path of the current page, without any domain information
2010-07-11 11:26:08 -04:00
#
2010-07-09 21:11:54 -04:00
def current_path
2016-11-30 12:03:29 -05:00
# Addressable parsing is more lenient than URI
uri = Addressable :: URI . parse ( current_url )
2017-01-12 20:55:26 -05:00
# If current_url ends up being nil, won't be able to call .path on a NilClass.
return nil if uri . nil?
2016-11-30 12:03:29 -05:00
# Addressable doesn't support opaque URIs - we want nil here
return nil if uri . scheme == " about "
path = uri . path
2011-04-07 11:08:32 -04:00
path if path and not path . empty?
2010-07-09 21:11:54 -04:00
end
2011-03-11 11:01:13 -05:00
##
#
# @return [String] Host of the current page
#
def current_host
2011-03-25 05:01:59 -04:00
uri = URI . parse ( current_url )
2011-04-07 10:18:11 -04:00
" #{ uri . scheme } :// #{ uri . host } " if uri . host
2011-03-11 11:01:13 -05:00
end
2010-07-11 11:26:08 -04:00
##
#
2010-07-14 18:24:59 -04:00
# @return [String] Fully qualified URL of the current page
2010-07-11 11:26:08 -04:00
#
def current_url
driver . current_url
end
2013-02-16 03:34:30 -05:00
2010-07-11 11:26:08 -04:00
##
#
# Navigate to the given URL. The URL can either be a relative URL or an absolute URL
# The behaviour of either depends on the driver.
#
# session.visit('/foo')
# session.visit('http://google.com')
#
2012-08-02 11:31:50 -04:00
# For drivers which can run against an external application, such as the selenium driver
2010-07-11 11:26:08 -04:00
# giving an absolute URL will navigate to that page. This allows testing applications
2012-08-02 11:31:50 -04:00
# running on remote servers. For these drivers, setting {Capybara.app_host} will make the
2010-07-11 11:26:08 -04:00
# remote server the default. For example:
#
# Capybara.app_host = 'http://google.com'
# session.visit('/') # visits the google homepage
#
2012-08-02 11:31:50 -04:00
# If {Capybara.always_include_port} is set to true and this session is running against
# a rack application, then the port that the rack application is running on will automatically
# be inserted into the URL. Supposing the app is running on port `4567`, doing something like:
#
# visit("http://google.com/test")
#
# Will actually navigate to `http://google.com:4567/test`.
#
2015-09-18 15:46:33 -04:00
# @param [#to_s] visit_uri The URL to navigate to. The parameter will be cast to a String.
2010-07-11 11:26:08 -04:00
#
2014-06-30 01:44:33 -04:00
def visit ( visit_uri )
2014-03-02 09:49:00 -05:00
raise_server_error!
2012-07-10 04:03:05 -04:00
@touched = true
2012-07-13 07:29:02 -04:00
2014-06-30 01:44:33 -04:00
visit_uri = URI . parse ( visit_uri . to_s )
2014-06-19 13:20:50 -04:00
2014-06-30 01:44:33 -04:00
uri_base = if @server
visit_uri . port = @server . port if Capybara . always_include_port && ( visit_uri . port == visit_uri . default_port )
URI . parse ( Capybara . app_host || " http:// #{ @server . host } : #{ @server . port } " )
else
Capybara . app_host && URI . parse ( Capybara . app_host )
2012-11-14 08:29:12 -05:00
end
2014-06-30 01:44:33 -04:00
# TODO - this is only for compatability with previous 2.x behavior that concatenated
# Capybara.app_host and a "relative" path - Consider removing in 3.0
# @abotalov brought up a good point about this behavior potentially being useful to people
# deploying to a subdirectory and/or single page apps where only the url fragment changes
if visit_uri . scheme . nil? && uri_base
visit_uri . path = uri_base . path + visit_uri . path
2012-07-13 07:29:02 -04:00
end
2014-06-30 01:44:33 -04:00
visit_uri = uri_base . merge ( visit_uri ) unless uri_base . nil?
driver . visit ( visit_uri . to_s )
2010-07-11 08:00:00 -04:00
end
2013-10-05 14:23:51 -04:00
##
#
# Move back a single entry in the browser's history.
#
def go_back
driver . go_back
end
##
#
# Move forward a single entry in the browser's history.
#
def go_forward
driver . go_forward
end
2010-07-11 11:26:08 -04:00
##
#
2013-08-26 09:13:30 -04:00
# Executes the given block within the context of a node. `within` takes the
# same options as `find`, as well as a block. For the duration of the
# block, any command to Capybara will be handled as though it were scoped
# to the given element.
2010-07-11 11:26:08 -04:00
#
2016-10-26 15:24:51 -04:00
# within(:xpath, './/div[@id="delivery-address"]') do
2016-10-04 14:10:29 -04:00
# fill_in('Street', with: '12 Main Street')
2010-07-11 11:26:08 -04:00
# end
#
2013-08-26 09:13:30 -04:00
# Just as with `find`, if multiple elements match the selector given to
# `within`, an error will be raised, and just as with `find`, this
# behaviour can be controlled through the `:match` and `:exact` options.
#
# It is possible to omit the first parameter, in that case, the selector is
# assumed to be of the type set in Capybara.default_selector.
2010-07-11 11:26:08 -04:00
#
# within('div#delivery-address') do
2016-10-04 14:10:29 -04:00
# fill_in('Street', with: '12 Main Street')
2010-07-11 11:26:08 -04:00
# end
#
2013-08-26 09:13:30 -04:00
# Note that a lot of uses of `within` can be replaced more succinctly with
# chaining:
#
2016-10-04 14:10:29 -04:00
# find('div#delivery-address').fill_in('Street', with: '12 Main Street')
2013-08-26 09:13:30 -04:00
#
2011-08-26 13:36:51 -04:00
# @overload within(*find_args)
# @param (see Capybara::Node::Finders#all)
#
# @overload within(a_node)
# @param [Capybara::Node::Base] a_node The node in whose scope the block should be evaluated
#
2013-08-26 09:13:30 -04:00
# @raise [Capybara::ElementNotFound] If the scope can't be found before time expires
2010-07-11 11:26:08 -04:00
#
2010-12-22 10:15:06 -05:00
def within ( * args )
2012-07-24 03:09:02 -04:00
new_scope = if args . first . is_a? ( Capybara :: Node :: Base ) then args . first else find ( * args ) end
2010-02-11 15:46:31 -05:00
begin
2010-07-09 19:38:57 -04:00
scopes . push ( new_scope )
2010-02-11 15:46:31 -05:00
yield
ensure
scopes . pop
end
2009-11-26 17:47:58 -05:00
end
2017-01-18 11:47:48 -05:00
alias_method :within_element , :within
2009-11-14 09:11:29 -05:00
2010-07-11 11:26:08 -04:00
##
#
# Execute the given block within the a specific fieldset given the id or legend of that fieldset.
#
2010-07-14 18:24:59 -04:00
# @param [String] locator Id or legend of the fieldset
2010-07-11 11:26:08 -04:00
#
2009-11-26 17:47:58 -05:00
def within_fieldset ( locator )
2011-08-27 17:57:12 -04:00
within :fieldset , locator do
2009-11-26 17:47:58 -05:00
yield
end
2009-11-10 16:48:31 -05:00
end
2010-07-11 11:26:08 -04:00
##
#
# Execute the given block within the a specific table given the id or caption of that table.
#
2010-07-14 18:24:59 -04:00
# @param [String] locator Id or caption of the table
2010-07-11 11:26:08 -04:00
#
2009-11-26 17:47:58 -05:00
def within_table ( locator )
2011-08-27 17:57:12 -04:00
within :table , locator do
2009-11-26 17:47:58 -05:00
yield
end
end
2010-01-01 12:29:30 -05:00
2016-12-08 19:57:45 -05:00
##
#
# 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
2010-07-11 11:26:08 -04:00
##
#
2016-07-15 14:00:14 -04:00
# Execute the given block within the given iframe using given frame, frame name/id or index.
# May not be supported by all drivers.
2010-07-11 11:26:08 -04:00
#
2016-07-15 14:00:14 -04:00
# @overload within_frame(element)
# @param [Capybara::Node::Element] frame element
2016-12-08 14:17:21 -05:00
# @overload within_frame([kind = :frame], locator, options = {})
# @param [Symobl] kind Optional selector type (:css, :xpath, :field, etc.) - Defaults to :frame
# @param [String] locator The locator for the given selector kind. For :frame this is the name/id of a frame/iframe element
2013-01-08 03:40:12 -05:00
# @overload within_frame(index)
2016-12-08 14:17:21 -05:00
# @param [Integer] index index of a frame (0 based)
def within_frame ( * args )
2016-12-08 19:57:45 -05:00
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
find ( :frame , * args )
when Symbol
find ( * args )
when Integer
idx = args [ 0 ]
all ( :frame , minimum : idx + 1 ) [ idx ]
else
2016-12-28 19:49:45 -05:00
raise TypeError
2016-12-08 19:57:45 -05:00
end
2016-07-15 14:00:14 -04:00
end
begin
2016-12-08 19:57:45 -05:00
switch_to_frame ( frame )
2016-07-15 14:00:14 -04:00
begin
yield
ensure
2016-12-08 19:57:45 -05:00
switch_to_frame ( :parent )
2016-07-15 14:00:14 -04:00
end
rescue Capybara :: NotSupportedByDriverError
# Support older driver frame API for now
if driver . respond_to? ( :within_frame )
2016-12-08 19:57:45 -05:00
begin
scopes . push ( :frame )
driver . within_frame ( frame ) do
yield
end
ensure
scopes . pop
2016-07-15 14:00:14 -04:00
end
else
raise
end
2009-11-26 17:47:58 -05:00
end
2010-08-27 15:00:08 -04:00
end
##
2014-04-08 17:28:16 -04:00
# @return [Capybara::Window] current window
2010-08-27 15:00:08 -04:00
#
2014-04-08 17:28:16 -04:00
def current_window
Window . new ( self , driver . current_window_handle )
end
##
# Get all opened windows.
# The order of windows in returned array is not defined.
# The driver may sort windows by their creation time but it's not required.
2010-08-27 15:00:08 -04:00
#
2014-04-08 17:28:16 -04:00
# @return [Array<Capybara::Window>] an array of all windows
2010-08-27 15:00:08 -04:00
#
2014-04-08 17:28:16 -04:00
def windows
driver . window_handles . map do | handle |
Window . new ( self , handle )
end
end
##
# Open new window.
# Current window doesn't change as the result of this call.
# It should be switched to explicitly.
#
# @return [Capybara::Window] window that has been opened
#
def open_new_window
window_opened_by do
driver . open_new_window
end
end
##
# @overload switch_to_window(&block)
# Switches to the first window for which given block returns a value other than false or nil.
2014-04-09 04:22:17 -04:00
# If window that matches block can't be found, the window will be switched back and `WindowError` will be raised.
2014-04-08 17:28:16 -04:00
# @example
# window = switch_to_window { title == 'Page title' }
# @raise [Capybara::WindowError] if no window matches given block
# @overload switch_to_window(window)
# @param window [Capybara::Window] window that should be switched to
# @raise [Capybara::Driver::Base#no_such_window_error] if unexistent (e.g. closed) window was passed
#
2014-04-09 04:22:17 -04:00
# @return [Capybara::Window] window that has been switched to
2014-04-08 17:28:16 -04:00
# @raise [Capybara::ScopeError] if this method is invoked inside `within`,
# `within_frame` or `within_window` methods
# @raise [ArgumentError] if both or neither arguments were provided
#
2015-07-21 22:16:52 -04:00
def switch_to_window ( window = nil , options = { } )
2015-09-03 19:41:00 -04:00
options , window = window , nil if window . is_a? Hash
2014-04-08 17:28:16 -04:00
block_given = block_given?
if window && block_given
raise ArgumentError , " `switch_to_window` can take either a block or a window, not both "
elsif ! window && ! block_given
raise ArgumentError , " `switch_to_window`: either window or block should be provided "
elsif scopes . size > 1
raise Capybara :: ScopeError , " `switch_to_window` is not supposed to be invoked from " \
" `within`'s, `within_frame`'s' or `within_window`'s' block. "
end
if window
driver . switch_to_window ( window . handle )
2014-04-09 04:22:17 -04:00
window
2014-04-08 17:28:16 -04:00
else
2016-08-30 13:18:06 -04:00
wait_time = Capybara :: Queries :: BaseQuery . wait ( options )
2015-07-21 22:16:52 -04:00
document . synchronize ( wait_time , errors : [ Capybara :: WindowError ] ) do
original_window_handle = driver . current_window_handle
begin
driver . window_handles . each do | handle |
driver . switch_to_window handle
if yield
return Window . new ( self , handle )
end
2014-04-09 04:22:17 -04:00
end
2015-07-21 22:16:52 -04:00
rescue = > e
driver . switch_to_window ( original_window_handle )
raise e
else
driver . switch_to_window ( original_window_handle )
raise Capybara :: WindowError , " Could not find a window matching block/lambda "
2014-04-08 17:28:16 -04:00
end
end
end
end
##
# This method does the following:
#
# 1. Switches to the given window (it can be located by window instance/lambda/string).
# 2. Executes the given block (within window located at previous step).
# 3. Switches back (this step will be invoked even if exception will happen at second step)
#
# @overload within_window(window) { do_something }
2014-04-10 03:20:27 -04:00
# @param window [Capybara::Window] instance of `Capybara::Window` class
2014-04-08 17:28:16 -04:00
# that will be switched to
2014-04-09 04:22:17 -04:00
# @raise [driver#no_such_window_error] if unexistent (e.g. closed) window was passed
2014-04-08 17:28:16 -04:00
# @overload within_window(proc_or_lambda) { do_something }
# @param lambda [Proc] lambda. First window for which lambda
# returns a value other than false or nil will be switched to.
# @example
# within_window(->{ page.title == 'Page title' }) { click_button 'Submit' }
# @raise [Capybara::WindowError] if no window matching lambda was found
# @overload within_window(string) { do_something }
# @deprecated Pass window or lambda instead
# @param [String] handle, name, url or title of the window
#
# @raise [Capybara::ScopeError] if this method is invoked inside `within`,
# `within_frame` or `within_window` methods
# @return value returned by the block
#
def within_window ( window_or_handle )
if window_or_handle . instance_of? ( Capybara :: Window )
original = current_window
2014-05-28 15:34:31 -04:00
switch_to_window ( window_or_handle ) unless original == window_or_handle
scopes << nil
2014-04-08 17:28:16 -04:00
begin
yield
ensure
2014-05-28 15:34:31 -04:00
@scopes . pop
2014-04-08 17:28:16 -04:00
switch_to_window ( original ) unless original == window_or_handle
end
elsif window_or_handle . is_a? ( Proc )
original = current_window
2014-04-09 04:22:17 -04:00
switch_to_window { window_or_handle . call }
scopes << nil
2014-04-08 17:28:16 -04:00
begin
yield
ensure
2014-05-28 15:34:31 -04:00
@scopes . pop
2014-04-08 17:28:16 -04:00
switch_to_window ( original )
end
else
offending_line = caller . first
file_line = offending_line . match ( / ^(.+?):( \ d+) / ) [ 0 ]
warn " DEPRECATION WARNING: Passing string argument to # within_window is deprecated. " \
" Pass window object or lambda. (called from #{ file_line } ) "
begin
2014-04-09 04:22:17 -04:00
scopes << nil
2014-04-08 17:28:16 -04:00
driver . within_window ( window_or_handle ) { yield }
ensure
2014-05-28 15:34:31 -04:00
@scopes . pop
2014-04-08 17:28:16 -04:00
end
end
end
##
# Get the window that has been opened by the passed block.
# It will wait for it to be opened (in the same way as other Capybara methods wait).
# It's better to use this method than `windows.last`
# {https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html#h_note_10 as order of windows isn't defined in some drivers}
#
# @param options [Hash]
2015-04-14 16:56:30 -04:00
# @option options [Numeric] :wait (Capybara.default_max_wait_time) maximum wait time
2014-04-08 17:28:16 -04:00
# @return [Capybara::Window] the window that has been opened within a block
# @raise [Capybara::WindowError] if block passed to window hasn't opened window
# or opened more than one window
#
def window_opened_by ( options = { } , & block )
old_handles = driver . window_handles
block . call
2016-08-30 13:18:06 -04:00
wait_time = Capybara :: Queries :: BaseQuery . wait ( options )
2014-04-08 17:28:16 -04:00
document . synchronize ( wait_time , errors : [ Capybara :: WindowError ] ) do
opened_handles = ( driver . window_handles - old_handles )
if opened_handles . size != 1
raise Capybara :: WindowError , " block passed to # window_opened_by " \
" opened #{ opened_handles . size } windows instead of 1 "
end
Window . new ( self , opened_handles . first )
end
2009-11-26 17:47:58 -05:00
end
2010-01-01 12:29:30 -05:00
2010-07-11 11:26:08 -04:00
##
#
# Execute the given script, not returning a result. This is useful for scripts that return
2011-12-08 14:59:17 -05:00
# complex objects, such as jQuery statements. +execute_script+ should be used over
2010-07-11 11:26:08 -04:00
# +evaluate_script+ whenever possible.
#
2010-07-14 18:24:59 -04:00
# @param [String] script A string of JavaScript to execute
2016-12-22 19:22:46 -05:00
# @param args Optional arguments that will be passed to the script. Driver support for this is optional and types of objects supported may differ between drivers
2010-07-11 11:26:08 -04:00
#
2016-12-22 19:22:46 -05:00
def execute_script ( script , * args )
2012-07-10 04:03:05 -04:00
@touched = true
2016-12-22 19:22:46 -05:00
if driver . method ( :execute_script ) . arity == 1
raise Capybara :: NotSupportedByDriverError , " The current driver does not support arguments being passed with execute_script " unless args . empty?
driver . execute_script ( script )
else
2017-01-03 23:41:37 -05:00
driver . execute_script ( script , * args . map { | arg | arg . is_a? ( Capybara :: Node :: Element ) ? arg . base : arg } )
2016-12-22 19:22:46 -05:00
end
2010-07-09 19:42:59 -04:00
end
2010-07-11 11:26:08 -04:00
##
#
# Evaluate the given JavaScript and return the result. Be careful when using this with
# scripts that return complex objects, such as jQuery statements. +execute_script+ might
# be a better alternative.
#
2010-07-14 18:24:59 -04:00
# @param [String] script A string of JavaScript to evaluate
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
2010-07-11 11:26:08 -04:00
#
2016-12-22 19:22:46 -05:00
def evaluate_script ( script , * args )
2012-07-10 04:03:05 -04:00
@touched = true
2016-12-22 19:22:46 -05:00
if driver . method ( :evaluate_script ) . arity == 1
raise Capybara :: NotSupportedByDriverError , " The current driver does not support arguments being passed with execute_script " unless args . empty?
driver . evaluate_script ( script )
else
2017-01-03 23:41:37 -05:00
driver . evaluate_script ( script , * args . map { | arg | arg . is_a? ( Capybara :: Node :: Element ) ? arg . base : arg } )
2016-12-22 19:22:46 -05:00
end
2010-07-09 19:42:59 -04:00
end
2013-04-01 18:41:55 -04:00
##
#
# Execute the block, accepting a alert.
#
2014-07-02 22:31:13 -04:00
# @!macro modal_params
# @overload $0(text, options = {}, &blk)
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any modal is matched
# @overload $0(options = {}, &blk)
2015-09-03 19:41:00 -04:00
# @option options [Numeric] :wait (Capybara.default_max_wait_time) Maximum time to wait for the modal to appear after executing the block.
2014-07-02 22:31:13 -04:00
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
2014-06-03 15:18:14 -04:00
#
2014-06-27 15:37:33 -04:00
def accept_alert ( text_or_options = nil , options = { } , & blk )
2016-11-18 13:56:47 -05:00
accept_modal ( :alert , text_or_options , options , & blk )
2013-04-01 18:41:55 -04:00
end
##
#
# Execute the block, accepting a confirm.
#
2014-07-02 22:31:13 -04:00
# @macro modal_params
2014-06-03 15:18:14 -04:00
#
2014-06-27 15:37:33 -04:00
def accept_confirm ( text_or_options = nil , options = { } , & blk )
2016-11-18 13:56:47 -05:00
accept_modal ( :confirm , text_or_options , options , & blk )
2013-04-01 18:41:55 -04:00
end
##
#
# Execute the block, dismissing a confirm.
#
2014-07-02 22:31:13 -04:00
# @macro modal_params
2014-06-03 15:18:14 -04:00
#
2014-06-27 15:37:33 -04:00
def dismiss_confirm ( text_or_options = nil , options = { } , & blk )
2016-11-18 13:56:47 -05:00
dismiss_modal ( :confirm , text_or_options , options , & blk )
2013-04-01 18:41:55 -04:00
end
##
#
2014-06-27 15:37:33 -04:00
# Execute the block, accepting a prompt, optionally responding to the prompt.
2013-04-01 18:41:55 -04:00
#
2014-07-02 22:31:13 -04:00
# @macro modal_params
2014-06-27 15:37:33 -04:00
# @option options [String] :with Response to provide to the prompt
2014-06-03 15:18:14 -04:00
#
2014-06-27 15:37:33 -04:00
def accept_prompt ( text_or_options = nil , options = { } , & blk )
2016-11-18 13:56:47 -05:00
accept_modal ( :prompt , text_or_options , options , & blk )
2013-04-01 18:41:55 -04:00
end
##
#
# Execute the block, dismissing a prompt.
#
2014-07-02 22:31:13 -04:00
# @macro modal_params
2014-06-03 15:18:14 -04:00
#
2014-06-27 15:37:33 -04:00
def dismiss_prompt ( text_or_options = nil , options = { } , & blk )
2016-11-18 13:56:47 -05:00
dismiss_modal ( :prompt , text_or_options , options , & blk )
2013-04-01 18:41:55 -04:00
end
2010-07-11 11:26:08 -04:00
##
#
2014-08-24 07:48:20 -04:00
# Save a snapshot of the page. If `Capybara.asset_host` is set it will inject `base` tag
# pointing to `asset_host`.
2012-07-24 03:05:50 -04:00
#
2016-04-05 12:33:19 -04:00
# If invoked without arguments it will save file to `Capybara.save_path`
# and file will be given randomly generated filename. If invoked with a relative path
# the path will be relative to `Capybara.save_path`, which is different from
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
# relative to Dir.pwd
2010-07-11 11:26:08 -04:00
#
2014-08-24 07:48:20 -04:00
# @param [String] path the path to where it should be saved
# @return [String] the path to which the file was saved
#
def save_page ( path = nil )
path = prepare_path ( path , 'html' )
File . write ( path , Capybara :: Helpers . inject_asset_host ( body ) , mode : 'wb' )
2012-07-24 03:05:50 -04:00
path
2011-02-12 15:18:47 -05:00
end
2012-07-24 03:05:50 -04:00
##
#
2014-08-24 07:48:20 -04:00
# Save a snapshot of the page and open it in a browser for inspection.
2012-07-24 03:05:50 -04:00
#
2016-04-05 12:33:19 -04:00
# If invoked without arguments it will save file to `Capybara.save_path`
# and file will be given randomly generated filename. If invoked with a relative path
# the path will be relative to `Capybara.save_path`, which is different from
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
# relative to Dir.pwd
2012-07-24 03:05:50 -04:00
#
2014-08-24 07:48:20 -04:00
# @param [String] path the path to where it should be saved
#
def save_and_open_page ( path = nil )
path = save_page ( path )
open_file ( path )
2010-07-09 19:42:59 -04:00
end
2012-07-10 00:50:15 -04:00
##
#
2014-08-24 07:48:20 -04:00
# Save a screenshot of page.
2012-07-10 00:50:15 -04:00
#
2016-04-05 12:33:19 -04:00
# If invoked without arguments it will save file to `Capybara.save_path`
# and file will be given randomly generated filename. If invoked with a relative path
# the path will be relative to `Capybara.save_path`, which is different from
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
# relative to Dir.pwd
2014-08-24 07:48:20 -04:00
#
# @param [String] path the path to where it should be saved
# @param [Hash] options a customizable set of options
# @return [String] the path to which the file was saved
def save_screenshot ( path = nil , options = { } )
path = prepare_path ( path , 'png' )
2012-07-10 00:50:15 -04:00
driver . save_screenshot ( path , options )
2014-03-21 22:09:15 -04:00
path
end
##
#
2014-08-24 07:48:20 -04:00
# Save a screenshot of the page and open it for inspection.
2014-03-21 22:09:15 -04:00
#
2016-04-05 12:33:19 -04:00
# If invoked without arguments it will save file to `Capybara.save_path`
# and file will be given randomly generated filename. If invoked with a relative path
# the path will be relative to `Capybara.save_path`, which is different from
# the previous behavior with `Capybara.save_and_open_page_path` where the relative path was
# relative to Dir.pwd
2014-03-21 22:09:15 -04:00
#
2014-08-24 07:48:20 -04:00
# @param [String] path the path to where it should be saved
# @param [Hash] options a customizable set of options
#
def save_and_open_screenshot ( path = nil , options = { } )
path = save_screenshot ( path , options )
open_file ( path )
2012-07-10 00:50:15 -04:00
end
2010-07-11 08:00:00 -04:00
def document
2011-07-13 09:39:17 -04:00
@document || = Capybara :: Node :: Document . new ( self , driver )
2010-07-11 08:00:00 -04:00
end
2011-03-25 09:22:47 -04:00
NODE_METHODS . each do | method |
2012-09-09 17:36:45 -04:00
define_method method do | * args , & block |
@touched = true
2013-02-17 10:19:26 -05:00
current_scope . send ( method , * args , & block )
2012-09-09 17:36:45 -04:00
end
2010-07-11 08:00:00 -04:00
end
2014-02-16 12:13:58 -05:00
DOCUMENT_METHODS . each do | method |
define_method method do | * args , & block |
document . send ( method , * args , & block )
2013-02-16 03:34:30 -05:00
end
end
2014-02-16 12:13:58 -05:00
def inspect
%( # <Capybara::Session> )
2013-02-16 03:34:30 -05:00
end
2013-02-17 10:19:26 -05:00
def current_scope
2016-12-08 19:57:45 -05:00
scope = scopes . last
scope = document if [ nil , :frame ] . include? scope
scope
2009-11-26 17:47:58 -05:00
end
2013-02-17 10:19:26 -05:00
private
2016-11-18 13:56:47 -05:00
def accept_modal ( type , text_or_options , options , & blk )
2016-11-21 19:28:45 -05:00
driver . accept_modal ( type , modal_options ( text_or_options , options ) , & blk )
2016-11-18 13:56:47 -05:00
end
def dismiss_modal ( type , text_or_options , options , & blk )
2016-11-21 19:28:45 -05:00
driver . dismiss_modal ( type , modal_options ( text_or_options , options ) , & blk )
end
def modal_options ( text_or_options , options )
2016-11-18 13:56:47 -05:00
text_or_options , options = nil , text_or_options if text_or_options . is_a? ( Hash )
options [ :text ] || = text_or_options unless text_or_options . nil?
options [ :wait ] || = Capybara . default_max_wait_time
2016-11-21 19:28:45 -05:00
options
2016-11-18 13:56:47 -05:00
end
2013-02-19 17:57:34 -05:00
2014-08-24 07:48:20 -04:00
def open_file ( path )
2014-03-21 22:09:15 -04:00
begin
require " launchy "
2014-08-24 07:48:20 -04:00
Launchy . open ( path )
2014-03-21 22:09:15 -04:00
rescue LoadError
2014-08-24 07:48:20 -04:00
warn " File saved to #{ path } . "
2014-03-21 22:09:15 -04:00
warn " Please install the launchy gem to open the file automatically. "
end
end
2014-08-24 07:48:20 -04:00
def prepare_path ( path , extension )
2016-04-05 12:33:19 -04:00
if Capybara . save_path || Capybara . save_and_open_page_path . nil?
path = File . expand_path ( path || default_fn ( extension ) , Capybara . save_path )
else
path = File . expand_path ( default_fn ( extension ) , Capybara . save_and_open_page_path ) if path . nil?
end
2014-08-24 07:48:20 -04:00
FileUtils . mkdir_p ( File . dirname ( path ) )
path
end
2016-04-05 12:33:19 -04:00
def default_fn ( extension )
2014-03-21 22:09:15 -04:00
timestamp = Time . new . strftime ( " %Y%m%d%H%M%S " )
2016-11-03 09:14:55 -04:00
" capybara- #{ timestamp } #{ rand ( 10 ** 10 ) } . #{ extension } "
2014-03-21 22:09:15 -04:00
end
2009-11-26 17:47:58 -05:00
def scopes
2014-04-09 04:22:17 -04:00
@scopes || = [ nil ]
2009-11-26 17:47:58 -05:00
end
2009-11-14 09:11:29 -05:00
end
2009-11-07 09:36:58 -05:00
end