2016-03-08 00:52:19 +00:00
# frozen_string_literal: true
2018-01-08 20:23:54 +00:00
2010-11-21 13:37:36 +00:00
module Capybara
module Node
##
#
2015-02-26 04:16:38 +00:00
# A {Capybara::Node::Element} represents a single element on the page. It is possible
2010-11-21 13:37:36 +00:00
# to interact with the contents of this element the same as with a document:
#
# session = Capybara::Session.new(:rack_test, my_app)
#
# bar = session.find('#bar') # from Capybara::Node::Finders
2016-10-04 18:10:29 +00:00
# bar.select('Baz', from: 'Quox') # from Capybara::Node::Actions
2010-11-21 13:37:36 +00:00
#
2015-02-26 04:16:38 +00:00
# {Capybara::Node::Element} also has access to HTML attributes and other properties of the
2010-11-21 13:37:36 +00:00
# element:
#
# bar.value
# bar.text
# bar[:title]
#
# @see Capybara::Node
#
class Element < Base
2016-07-18 21:16:22 +00:00
def initialize ( session , base , query_scope , query )
2011-07-13 13:39:17 +00:00
super ( session , base )
2016-07-18 21:16:22 +00:00
@query_scope = query_scope
2012-06-11 15:29:58 +00:00
@query = query
2016-11-21 19:53:59 +00:00
@allow_reload = false
2012-06-11 15:29:58 +00:00
end
def allow_reload!
@allow_reload = true
2011-07-13 13:39:17 +00:00
end
2010-11-21 13:37:36 +00:00
##
#
# @return [Object] The native element from the driver, this allows access to driver specific methods
#
def native
2012-02-01 13:16:17 +00:00
synchronize { base . native }
2010-11-21 13:37:36 +00:00
end
##
#
2013-02-17 13:46:37 +00:00
# Retrieve the text of the element. If `Capybara.ignore_hidden_elements`
# is `true`, which it is by default, then this will return only text
# which is visible. The exact semantics of this may differ between
# drivers, but generally any text within elements with `display:none` is
# ignored. This behaviour can be overridden by passing `:all` to this
# method.
2010-11-21 13:37:36 +00:00
#
2018-05-17 21:45:53 +00:00
# @param type [:all, :visible] Whether to return only visible or all text
2013-03-29 02:15:07 +00:00
# @return [String] The text of the element
2013-02-17 13:46:37 +00:00
#
2018-01-09 22:05:50 +00:00
def text ( type = nil )
2018-05-14 21:30:34 +00:00
type || = :all unless session_options . ignore_hidden_elements || session_options . visible_text_only
2018-05-16 19:47:08 +00:00
synchronize { type == :all ? base . all_text : base . visible_text }
2010-11-21 13:37:36 +00:00
end
##
#
# Retrieve the given attribute
#
# element[:title] # => HTML title attribute
#
# @param [Symbol] attribute The attribute to retrieve
# @return [String] The value of the attribute
#
def [] ( attribute )
2012-02-01 13:16:17 +00:00
synchronize { base [ attribute ] }
2010-11-21 13:37:36 +00:00
end
2018-06-19 20:34:54 +00:00
##
#
# Retrieve the given CSS styles
#
2018-06-20 18:43:21 +00:00
# element.style('color', 'font-size') # => Computed values of CSS 'color' and 'font-size' styles
#
# @param [String] Names of the desired CSS properties
# @return [Hash] Hash of the CSS property names to computed values
2018-06-19 20:34:54 +00:00
#
def style ( * styles )
styles = styles . flatten . map ( & :to_s )
raise ArgumentError , " You must specify at least one CSS style " if styles . empty?
2018-06-20 18:43:21 +00:00
begin
2018-06-19 20:34:54 +00:00
synchronize { base . style ( styles ) }
rescue NotImplementedError = > e
begin
evaluate_script ( STYLE_SCRIPT , * styles )
rescue Capybara :: NotSupportedByDriverError
raise e
end
end
end
2010-11-21 13:37:36 +00:00
##
#
# @return [String] The value of the form element
#
def value
2012-02-01 13:16:17 +00:00
synchronize { base . value }
2010-11-21 13:37:36 +00:00
end
##
#
# Set the value of the form element to the given value.
#
# @param [String] value The new value
2018-05-18 15:22:14 +00:00
# @param [Hash{}] options Driver specific options for how to set the value. Take default values from {Capybara#default_set_options}
2010-11-21 13:37:36 +00:00
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2016-08-17 23:14:39 +00:00
def set ( value , ** options )
2016-08-17 19:49:08 +00:00
raise Capybara :: ReadOnlyElementError , " Attempt to set readonly element with value: #{ value } " if readonly?
2018-05-18 17:29:53 +00:00
options = session_options . default_set_options . to_h . merge ( options )
2018-01-13 00:57:41 +00:00
synchronize { base . set ( value , options ) }
2018-01-11 00:20:07 +00:00
self
2010-11-21 13:37:36 +00:00
end
##
#
# Select this node if is an option element inside a select tag
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2010-11-21 13:37:36 +00:00
def select_option
2015-06-05 17:59:26 +00:00
warn " Attempt to select disabled option: #{ value || text } " if disabled?
2012-02-01 13:16:17 +00:00
synchronize { base . select_option }
2018-01-13 00:57:41 +00:00
self
2010-11-21 13:37:36 +00:00
end
##
#
# Unselect this node if is an option element inside a multiple select tag
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2010-11-21 13:37:36 +00:00
def unselect_option
2012-02-01 13:16:17 +00:00
synchronize { base . unselect_option }
2018-01-13 00:57:41 +00:00
self
2010-11-21 13:37:36 +00:00
end
##
#
# Click the Element
#
2017-12-29 20:37:08 +00:00
# @!macro click_modifiers
2018-05-17 21:45:53 +00:00
# Both x: and y: must be specified if an offset is wanted, if not specified the click will occur at the middle of the element
# @overload $0(*modifier_keys, **offset)
# @param *modifier_keys [:alt, :control, :meta, :shift] ([]) Keys to be held down when clicking
# @option offset [Integer] x X coordinate to offset the click location from the top left corner of the element
# @option offset [Integer] y Y coordinate to offset the click location from the top left corner of the element
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2018-01-08 20:33:47 +00:00
def click ( * keys , ** offset )
2018-01-10 19:02:39 +00:00
synchronize { base . click ( keys , offset ) }
2018-01-13 00:57:41 +00:00
self
2010-11-21 13:37:36 +00:00
end
2013-05-10 16:55:17 +00:00
##
#
# Right Click the Element
#
2017-12-29 20:37:08 +00:00
# @macro click_modifiers
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2018-01-08 20:33:47 +00:00
def right_click ( * keys , ** offset )
2018-01-10 19:02:39 +00:00
synchronize { base . right_click ( keys , offset ) }
2018-01-13 00:57:41 +00:00
self
2013-05-10 16:55:17 +00:00
end
##
#
# Double Click the Element
#
2017-12-29 20:37:08 +00:00
# @macro click_modifiers
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2018-01-08 20:33:47 +00:00
def double_click ( * keys , ** offset )
2018-01-10 19:02:39 +00:00
synchronize { base . double_click ( keys , offset ) }
2018-01-13 00:57:41 +00:00
self
2013-05-10 16:55:17 +00:00
end
2015-06-05 17:59:26 +00:00
2015-01-23 20:23:57 +00:00
##
#
# Send Keystrokes to the Element
#
2015-08-25 20:35:43 +00:00
# @overload send_keys(keys, ...)
2018-05-17 21:45:53 +00:00
# @param keys [String, Symbol, Array<String,Symbol>]
2015-01-23 20:23:57 +00:00
#
# Examples:
#
# element.send_keys "foo" #=> value: 'foo'
# element.send_keys "tet", :left, "s" #=> value: 'test'
2015-08-25 20:35:43 +00:00
# element.send_keys [:control, 'a'], :space #=> value: ' ' - assuming ctrl-a selects all contents
2015-01-23 20:23:57 +00:00
#
# Symbols supported for keys
# :cancel
# :help
# :backspace
# :tab
# :clear
# :return
# :enter
# :shift
# :control
# :alt
# :pause
# :escape
# :space
# :page_up
# :page_down
# :end
# :home
# :left
# :up
# :right
# :down
# :insert
# :delete
# :semicolon
# :equals
# :numpad0
# :numpad1
# :numpad2
# :numpad3
# :numpad4
# :numpad5
# :numpad6
# :numpad7
# :numpad8
# :numpad9
# :multiply - numeric keypad *
# :add - numeric keypad +
# :separator - numeric keypad 'separator' key ??
# :subtract - numeric keypad -
# :decimal - numeric keypad .
# :divide - numeric keypad /
# :f1
# :f2
# :f3
# :f4
# :f5
# :f6
# :f7
# :f8
# :f9
# :f10
# :f11
# :f12
2015-06-05 17:59:26 +00:00
# :meta
2015-01-23 20:23:57 +00:00
# :command - alias of :meta
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2015-01-23 20:23:57 +00:00
def send_keys ( * args )
synchronize { base . send_keys ( * args ) }
2018-01-13 00:57:41 +00:00
self
2015-01-23 20:23:57 +00:00
end
2013-05-10 16:55:17 +00:00
2013-02-25 18:37:25 +00:00
##
#
# Hover on the Element
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2013-02-25 18:37:25 +00:00
def hover
synchronize { base . hover }
2018-01-13 00:57:41 +00:00
self
2013-02-25 18:37:25 +00:00
end
2010-11-21 13:37:36 +00:00
##
#
# @return [String] The tag name of the element
#
def tag_name
2012-02-01 13:16:17 +00:00
synchronize { base . tag_name }
2010-11-21 13:37:36 +00:00
end
##
#
# Whether or not the element is visible. Not all drivers support CSS, so
# the result may be inaccurate.
#
# @return [Boolean] Whether the element is visible
#
def visible?
2012-02-01 13:16:17 +00:00
synchronize { base . visible? }
2010-11-21 13:37:36 +00:00
end
2010-12-23 18:38:44 +00:00
##
#
# Whether or not the element is checked.
#
# @return [Boolean] Whether the element is checked
#
def checked?
2012-02-01 13:16:17 +00:00
synchronize { base . checked? }
2010-12-23 18:38:44 +00:00
end
##
#
# Whether or not the element is selected.
#
# @return [Boolean] Whether the element is selected
#
def selected?
2012-02-01 13:16:17 +00:00
synchronize { base . selected? }
2010-12-23 18:38:44 +00:00
end
2013-01-29 09:35:23 +00:00
##
#
# Whether or not the element is disabled.
#
# @return [Boolean] Whether the element is disabled
#
def disabled?
synchronize { base . disabled? }
end
2016-06-11 19:09:23 +00:00
##
#
# Whether or not the element is readonly.
#
# @return [Boolean] Whether the element is readonly
#
def readonly?
synchronize { base . readonly? }
end
##
#
# Whether or not the element supports multiple results.
#
# @return [Boolean] Whether the element supports multiple results.
#
def multiple?
synchronize { base . multiple? }
end
2010-11-21 13:37:36 +00:00
##
#
# An XPath expression describing where on the page the element can be found
#
# @return [String] An XPath expression
#
def path
2012-02-01 13:16:17 +00:00
synchronize { base . path }
2010-11-21 13:37:36 +00:00
end
##
#
# Trigger any event on the current element, for example mouseover or focus
# events. Does not work in Selenium.
#
# @param [String] event The name of the event to trigger
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2010-11-21 13:37:36 +00:00
def trigger ( event )
2012-02-01 13:16:17 +00:00
synchronize { base . trigger ( event ) }
2018-01-13 00:57:41 +00:00
self
2010-11-21 13:37:36 +00:00
end
##
#
# Drag the element to the given other element.
#
# source = page.find('#foo')
# target = page.find('#bar')
# source.drag_to(target)
#
2015-02-26 04:16:38 +00:00
# @param [Capybara::Node::Element] node The element to drag to
2010-11-21 13:37:36 +00:00
#
2016-10-10 21:43:40 +00:00
# @return [Capybara::Node::Element] The element
2010-11-21 13:37:36 +00:00
def drag_to ( node )
2012-02-01 13:16:17 +00:00
synchronize { base . drag_to ( node . base ) }
2018-01-13 00:57:41 +00:00
self
2011-07-13 13:39:17 +00:00
end
2018-05-18 21:13:53 +00:00
##
#
# Execute the given JS in the context of the element not returning a result. This is useful for scripts that return
# complex objects, such as jQuery statements. +execute_script+ should be used over
# +evaluate_script+ whenever possible. `this` in the script will refer to the element this is called on.
#
# @param [String] script A string of JavaScript to execute
# @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
#
def execute_script ( script , * args )
session . execute_script ( << ~ JS , self , * args )
( function ( ) {
#{script}
} ) . apply ( arguments [ 0 ] , Array . prototype . slice . call ( arguments , 1 ) ) ;
JS
end
##
#
# Evaluate the given JS in the context of the element 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. `this` in the script will refer to the element this is called on.
#
# @param [String] script A string of JavaScript to evaluate
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
#
def evaluate_script ( script , * args )
session . evaluate_script ( << ~ JS , self , * args )
( function ( ) {
return #{script}
} ) . apply ( arguments [ 0 ] , Array . prototype . slice . call ( arguments , 1 ) ) ;
JS
end
2018-06-03 18:20:29 +00:00
##
#
# Evaluate the given JavaScript in the context of the element and obtain the result from a
# callback function which will be passed as the last argument to the script. `this` in the
# script will refer to the element this is called on
#
# @param [String] script A string of JavaScript to evaluate
# @return [Object] The result of the evaluated JavaScript (may be driver specific)
#
def evaluate_async_script ( script , * args )
session . evaluate_async_script ( << ~ JS , self , * args )
( function ( ) {
#{script}
} ) . apply ( arguments [ 0 ] , Array . prototype . slice . call ( arguments , 1 ) ) ;
JS
end
2011-07-13 13:39:17 +00:00
def reload
2012-06-11 15:29:58 +00:00
if @allow_reload
2013-03-14 09:03:49 +00:00
begin
2016-07-18 21:16:22 +00:00
reloaded = query_scope . reload . first ( @query . name , @query . locator , @query . options )
2013-03-14 09:03:49 +00:00
@base = reloaded . base if reloaded
2018-05-14 21:30:34 +00:00
rescue StandardError = > e
2013-03-14 09:03:49 +00:00
raise e unless catch_error? ( e )
end
2012-06-11 15:29:58 +00:00
end
2011-07-13 13:39:17 +00:00
self
2010-11-21 13:37:36 +00:00
end
def inspect
2017-08-09 03:36:45 +00:00
%( # <Capybara::Node::Element tag=" #{ base . tag_name } " path=" #{ base . path } "> )
2014-04-22 16:06:34 +00:00
rescue NotSupportedByDriverError
2017-08-09 03:36:45 +00:00
%( # <Capybara::Node::Element tag=" #{ base . tag_name } "> )
2018-05-14 21:30:34 +00:00
rescue StandardError = > e
2018-01-09 22:05:50 +00:00
raise unless session . driver . invalid_element_errors . any? { | et | e . is_a? ( et ) }
%( Obsolete # <Capybara::Node::Element> )
2010-11-21 13:37:36 +00:00
end
2018-06-19 20:34:54 +00:00
STYLE_SCRIPT = << ~ JS
( function ( ) {
var s = window . getComputedStyle ( this ) ;
var result = { } ;
for ( var i = arguments . length ; i - - ; ) {
var property_name = arguments [ i ] ;
result [ property_name ] = s . getPropertyValue ( property_name ) ;
}
return result ;
} ) . apply ( this , arguments )
2018-06-20 18:43:21 +00:00
JS
2010-11-21 13:37:36 +00:00
end
end
end