1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
|
# frozen_string_literal: true
module Capybara
module Node
##
#
# A {Capybara::Node::Simple} is a simpler version of {Capybara::Node::Base} which
# includes only {Capybara::Node::Finders} and {Capybara::Node::Matchers} and does
# not include {Capybara::Node::Actions}. This type of node is returned when
# using {Capybara.string}.
#
# It is useful in that it does not require a session, an application or a driver,
# but can still use Capybara's finders and matchers on any string that contains HTML.
#
class Simple
include Capybara::Node::Finders
include Capybara::Node::Matchers
include Capybara::Node::DocumentMatchers
attr_reader :native
def initialize(native)
native = Capybara::HTML(native) if native.is_a?(String)
@native = native
end
##
#
# @return [String] The text of the element
#
def text(_type = nil, normalize_ws: false)
txt = native.text
normalize_ws ? txt.gsub(/[[:space:]]+/, ' ').strip : txt
end
##
#
# Retrieve the given attribute
#
# element[:title] # => HTML title attribute
#
# @param [Symbol] name The attribute name to retrieve
# @return [String] The value of the attribute
#
def [](name)
attr_name = name.to_s
if attr_name == 'value'
value
elsif (tag_name == 'input') && (native[:type] == 'checkbox') && (attr_name == 'checked')
native['checked'] == 'checked'
else
native[attr_name]
end
end
##
#
# @return [String] The tag name of the element
#
def tag_name
native.node_name
end
##
#
# An XPath expression describing where on the page the element can be found
#
# @return [String] An XPath expression
#
def path
native.path
end
##
#
# @return [String] The value of the form element
#
def value
if tag_name == 'textarea'
native['_capybara_raw_value']
elsif tag_name == 'select'
selected_options = find_xpath('.//option[@selected]')
if multiple?
selected_options.map(&method(:option_value))
else
option_value(selected_options.first || find_xpath('.//option').first)
end
elsif tag_name == 'input' && %w[radio checkbox].include?(native[:type])
native[:value] || 'on'
else
native[:value]
end
end
##
#
# Whether or not the element is visible. Does not support CSS, so
# the result may be inaccurate.
#
# @param [Boolean] check_ancestors Whether to inherit visibility from ancestors
# @return [Boolean] Whether the element is visible
#
def visible?(check_ancestors = true) # rubocop:disable Style/OptionalBooleanParameter
return false if (tag_name == 'input') && (native[:type] == 'hidden')
return false if tag_name == 'template'
if check_ancestors
!find_xpath(VISIBILITY_XPATH)
else
# No need for an xpath if only checking the current element
!(native.key?('hidden') ||
/display:\s?none/.match?(native[:style] || '') ||
%w[script head style].include?(tag_name))
end
end
##
#
# Whether or not the element is checked.
#
# @return [Boolean] Whether the element is checked
#
def checked?
native.has_attribute?('checked')
end
##
#
# Whether or not the element is disabled.
#
# @return [Boolean] Whether the element is disabled
def disabled?
native.has_attribute?('disabled') &&
%w[button input select textarea optgroup option menuitem fieldset].include?(tag_name)
end
##
#
# Whether or not the element is selected.
#
# @return [Boolean] Whether the element is selected
#
def selected?
native.has_attribute?('selected')
end
def multiple?
native.has_attribute?('multiple')
end
def readonly?
native.has_attribute?('readonly')
end
def synchronize(_seconds = nil)
yield # simple nodes don't need to wait
end
def allow_reload!(*)
# no op
end
##
#
# @return [String] The title of the document
def title
native.title
end
def inspect
%(#<Capybara::Node::Simple tag="#{tag_name}" path="#{path}">)
end
# @api private
def find_css(css, **_options)
native.css(css)
end
# @api private
def find_xpath(xpath, **_options)
native.xpath(xpath)
end
# @api private
def session_options
Capybara.session_options
end
# @api private
def initial_cache
{}
end
def ==(other)
eql?(other) || (other.respond_to?(:native) && native == other.native)
end
private
def option_value(option)
return nil if option.nil?
option[:value] || option.content
end
VISIBILITY_XPATH = XPath.generate do |x|
x.ancestor_or_self[
x.attr(:style)[x.contains('display:none') | x.contains('display: none')] |
x.attr(:hidden) |
x.qname.one_of('script', 'head') |
(~x.self(:summary) & XPath.parent(:details)[!XPath.attr(:open)])
].boolean
end.to_s.freeze
end
end
end
|