2016-03-07 19:52:19 -05:00
# frozen_string_literal: true
2018-01-08 15:23:54 -05:00
2011-04-11 01:24:00 -04:00
class Capybara :: Selenium :: Node < Capybara :: Driver :: Node
2013-02-17 08:58:41 -05:00
def visible_text
2012-08-01 07:24:43 -04:00
# Selenium doesn't normalize Unicode whitespace.
Capybara :: Helpers . normalize_whitespace ( native . text )
2011-04-11 01:24:00 -04:00
end
2013-02-17 08:58:41 -05:00
def all_text
2017-03-07 19:32:02 -05:00
text = driver . execute_script ( " return arguments[0].textContent " , self )
2013-02-17 08:58:41 -05:00
Capybara :: Helpers . normalize_whitespace ( text )
end
2011-04-11 01:24:00 -04:00
def [] ( name )
2012-10-26 09:14:56 -04:00
native . attribute ( name . to_s )
2011-04-11 01:24:00 -04:00
rescue Selenium :: WebDriver :: Error :: WebDriverError
nil
end
def value
2016-06-11 15:09:23 -04:00
if tag_name == " select " and multiple?
2017-08-01 16:59:17 -04:00
native . find_elements ( :css , " option:checked " ) . map { | n | n [ :value ] || n . text }
2011-04-11 01:24:00 -04:00
else
2011-04-28 15:30:01 -04:00
native [ :value ]
2011-04-11 01:24:00 -04:00
end
end
2015-04-13 00:25:13 -04:00
##
#
# Set the value of the form element to the given value.
#
# @param [String] value The new value
# @param [Hash{}] options Driver specific options for how to set the value
# @option options [Symbol,Array] :clear (nil) The method used to clear the previous value <br/>
# nil => clear via javascript <br/>
# :none => append the new value to the existing value <br/>
# :backspace => send backspace keystrokes to clear the field <br/>
# Array => an array of keys to send before the value being set, e.g. [[:command, 'a'], :backspace]
2016-08-17 19:14:39 -04:00
def set ( value , ** options )
2018-01-11 19:45:50 -05:00
raise ArgumentError , " Value cannot be an Array when 'multiple' attribute is not present. Not a #{ value . class } " if value . is_a? ( Array ) && ! multiple?
2012-10-26 09:14:56 -04:00
tag_name = self . tag_name
type = self [ :type ]
2017-08-01 16:59:17 -04:00
case tag_name
when 'input'
case type
when 'radio'
click
when 'checkbox'
2018-01-11 19:45:50 -05:00
click if value ^ checked?
2017-08-01 16:59:17 -04:00
when 'file'
2017-11-13 16:04:47 -05:00
set_file ( value )
2016-10-18 14:09:26 -04:00
else
2017-08-01 16:59:17 -04:00
set_text ( value , options )
2016-10-18 14:09:26 -04:00
end
2017-08-01 16:59:17 -04:00
when 'textarea'
set_text ( value , options )
else
2018-01-09 17:05:50 -05:00
set_content_editable ( value ) if content_editable?
2011-04-11 01:24:00 -04:00
end
end
def select_option
2016-06-10 20:22:17 -04:00
native . click unless selected? || disabled?
2011-04-11 01:24:00 -04:00
end
def unselect_option
2018-01-09 17:05:50 -05:00
raise Capybara :: UnselectNotAllowed , " Cannot unselect option from single select box. " unless select_node . multiple?
2012-02-01 08:23:17 -05:00
native . click if selected?
2011-04-11 01:24:00 -04:00
end
2018-01-09 17:05:50 -05:00
def click ( keys = [ ] , options = { } )
2018-01-08 15:33:47 -05:00
if keys . empty? && ! ( options [ :x ] && options [ :y ] )
2017-12-29 15:37:08 -05:00
native . click
else
scroll_if_needed do
2018-01-08 15:33:47 -05:00
action_with_modifiers ( keys , options ) do | a |
2017-12-29 15:37:08 -05:00
if options [ :x ] && options [ :y ]
a . click
else
a . click ( native )
end
end
end
end
2017-10-02 14:55:13 -04:00
rescue = > e
2017-10-02 18:39:25 -04:00
if e . is_a? ( :: Selenium :: WebDriver :: Error :: ElementClickInterceptedError ) ||
e . message =~ / Other element would receive the click /
2017-10-02 14:55:13 -04:00
begin
driver . execute_script ( " arguments[0].scrollIntoView({behavior: 'instant', block: 'center', inline: 'center'}) " , self )
2018-01-09 17:05:50 -05:00
rescue # Swallow error if scrollIntoView with options isn't supported
2017-10-02 14:55:13 -04:00
end
end
raise e
2011-04-11 01:24:00 -04:00
end
2015-04-13 12:24:13 -04:00
2018-01-09 17:05:50 -05:00
def right_click ( keys = [ ] , options = { } )
2017-11-16 21:32:26 -05:00
scroll_if_needed do
2018-01-08 15:33:47 -05:00
action_with_modifiers ( keys , options ) do | a |
2017-12-29 15:37:08 -05:00
if options [ :x ] && options [ :y ]
a . context_click
else
a . context_click ( native )
end
end
2017-11-16 21:32:26 -05:00
end
2013-05-10 12:55:17 -04:00
end
2015-04-13 12:24:13 -04:00
2018-01-09 17:05:50 -05:00
def double_click ( keys = [ ] , options = { } )
2017-11-16 21:32:26 -05:00
scroll_if_needed do
2018-01-08 15:33:47 -05:00
action_with_modifiers ( keys , options ) do | a |
2017-12-29 15:37:08 -05:00
if options [ :x ] && options [ :y ]
a . double_click
else
a . double_click ( native )
end
end
2017-11-16 21:32:26 -05:00
end
2013-05-10 12:55:17 -04:00
end
2015-04-13 12:24:13 -04:00
2015-01-23 15:23:57 -05:00
def send_keys ( * args )
native . send_keys ( * args )
end
2011-04-11 01:24:00 -04:00
2013-02-25 13:37:25 -05:00
def hover
2017-11-16 21:32:26 -05:00
scroll_if_needed do
driver . browser . action . move_to ( native ) . perform
end
2013-02-25 13:37:25 -05:00
end
2012-12-14 19:06:50 -05:00
2011-04-11 01:24:00 -04:00
def drag_to ( element )
2017-11-16 21:32:26 -05:00
scroll_if_needed do
driver . browser . action . drag_and_drop ( native , element . native ) . perform
end
2011-04-11 01:24:00 -04:00
end
def tag_name
2012-10-26 09:14:56 -04:00
native . tag_name . downcase
2011-04-11 01:24:00 -04:00
end
def visible?
displayed = native . displayed?
displayed and displayed != " false "
end
def selected?
selected = native . selected?
selected and selected != " false "
end
2016-06-11 15:09:23 -04:00
alias :checked? :selected?
2011-04-11 01:24:00 -04:00
2013-01-29 05:45:24 -05:00
def disabled?
2017-01-02 17:08:35 -05:00
# workaround for selenium-webdriver/geckodriver reporting elements as enabled when they are nested in disabling elements
2017-01-02 20:17:35 -05:00
if driver . marionette?
2018-01-09 17:05:50 -05:00
if %w[ option optgroup ] . include? tag_name
2017-01-03 16:45:49 -05:00
! native . enabled? || find_xpath ( " parent::*[self::optgroup or self::select] " ) [ 0 ] . disabled?
2017-01-02 17:08:35 -05:00
else
2017-01-03 16:45:49 -05:00
! native . enabled? || ! find_xpath ( " parent::fieldset[@disabled] | ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]] " ) . empty?
2017-01-02 17:08:35 -05:00
end
else
! native . enabled?
end
2013-01-29 05:45:24 -05:00
end
2016-06-11 15:09:23 -04:00
def readonly?
readonly = self [ :readonly ]
readonly and readonly != " false "
end
def multiple?
multiple = self [ :multiple ]
multiple and multiple != " false "
end
2011-04-11 01:24:00 -04:00
2017-08-01 16:59:17 -04:00
def content_editable?
native . attribute ( 'isContentEditable' )
end
2013-02-19 12:03:26 -05:00
def find_xpath ( locator )
2011-04-11 01:24:00 -04:00
native . find_elements ( :xpath , locator ) . map { | n | self . class . new ( driver , n ) }
end
2012-12-14 19:06:50 -05:00
2013-02-19 12:03:26 -05:00
def find_css ( locator )
native . find_elements ( :css , locator ) . map { | n | self . class . new ( driver , n ) }
end
2012-12-14 19:06:50 -05:00
2012-11-19 21:57:09 -05:00
def == ( other )
native == other . native
end
2015-08-09 12:55:56 -04:00
def path
2015-08-10 09:49:15 -04:00
path = find_xpath ( 'ancestor::*' ) . reverse
2015-08-09 12:55:56 -04:00
path . unshift self
result = [ ]
2017-11-13 16:04:47 -05:00
while ( node = path . shift )
2015-08-09 12:55:56 -04:00
parent = path . first
if parent
siblings = parent . find_xpath ( node . tag_name )
if siblings . size == 1
result . unshift node . tag_name
else
index = siblings . index ( node )
2018-01-09 17:05:50 -05:00
result . unshift " #{ node . tag_name } [ #{ index + 1 } ] "
2015-08-09 12:55:56 -04:00
end
else
result . unshift node . tag_name
end
end
'/' + result . join ( '/' )
end
2011-04-11 01:24:00 -04:00
private
2018-01-09 17:05:50 -05:00
2011-04-11 01:24:00 -04:00
# a reference to the select node if this is an option node
def select_node
2017-08-01 16:59:17 -04:00
find_xpath ( './ancestor::select[1]' ) . first
end
2017-05-01 21:39:08 -04:00
def set_text ( value , clear : nil , ** )
2017-09-17 16:27:45 -04:00
if value . to_s . empty? && clear . nil?
2017-08-01 16:59:17 -04:00
native . clear
2018-01-08 15:23:54 -05:00
elsif clear == :backspace
# Clear field by sending the correct number of backspace keys.
backspaces = [ :backspace ] * self . value . to_s . length
native . send_keys ( * ( backspaces + [ value . to_s ] ) )
elsif clear == :none
native . send_keys ( value . to_s )
elsif clear . is_a? Array
native . send_keys ( * clear , value . to_s )
2017-08-01 16:59:17 -04:00
else
2018-01-08 15:23:54 -05:00
# Clear field by JavaScript assignment of the value property.
# Script can change a readonly element which user input cannot, so
# don't execute if readonly.
driver . execute_script " arguments[0].value = '' " , self
native . send_keys ( value . to_s )
2017-08-01 16:59:17 -04:00
end
2011-04-11 01:24:00 -04:00
end
2017-11-16 21:32:26 -05:00
2018-01-08 15:23:54 -05:00
def scroll_if_needed
yield
2017-11-16 21:32:26 -05:00
rescue :: Selenium :: WebDriver :: Error :: MoveTargetOutOfBoundsError
script = <<-JS
try {
arguments [ 0 ] . scrollIntoView ( { behavior : 'instant' , block : 'center' , inline : 'center' } ) ;
} catch ( e ) {
arguments [ 0 ] . scrollIntoView ( true ) ;
}
JS
driver . execute_script ( script , self )
2018-01-08 15:23:54 -05:00
yield
2017-11-16 21:32:26 -05:00
end
2017-11-13 16:04:47 -05:00
2018-01-09 17:05:50 -05:00
def set_file ( value ) # rubocop:disable Naming/AccessorMethodName
2017-11-13 16:04:47 -05:00
path_names = value . to_s . empty? ? [ ] : value
if driver . chrome?
native . send_keys ( Array ( path_names ) . join ( " \n " ) )
else
native . send_keys ( * path_names )
end
end
2018-01-09 17:05:50 -05:00
def set_content_editable ( value ) # rubocop:disable Naming/AccessorMethodName
# Ensure we are focused on the element
2017-11-13 16:04:47 -05:00
click
script = <<-JS
var range = document . createRange ( ) ;
var sel = window . getSelection ( ) ;
arguments [ 0 ] . focus ( ) ;
range . selectNodeContents ( arguments [ 0 ] ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
JS
driver . execute_script script , self
2018-01-03 19:03:10 -05:00
# The action api has a speed problem but both chrome and firefox 58 raise errors
# if we use the faster direct send_keys. For now just send_keys to the element
# we've already focused.
# native.send_keys(value.to_s)
driver . browser . action . send_keys ( value . to_s ) . perform
2017-11-13 16:04:47 -05:00
end
2017-12-29 15:37:08 -05:00
2018-01-08 15:33:47 -05:00
def action_with_modifiers ( keys , x : nil , y : nil )
2017-12-29 15:37:08 -05:00
actions = driver . browser . action
actions . move_to ( native , x , y )
modifiers_down ( actions , keys )
yield actions
modifiers_up ( actions , keys )
actions . perform
ensure
a = driver . browser . action
a . release_actions if a . respond_to? ( :release_actions )
end
def modifiers_down ( actions , keys )
keys . each do | key |
key = case key
when :ctrl then :control
when :command , :cmd then :meta
else
key
end
actions . key_down ( key )
end
end
def modifiers_up ( actions , keys )
keys . each do | key |
key = case key
when :ctrl then :control
when :command , :cmd then :meta
else
key
end
actions . key_up ( key )
end
end
2011-04-11 01:24:00 -04:00
end