lib/capybara/node/actions.rb in capybara-3.0.3 vs lib/capybara/node/actions.rb in capybara-3.1.0
- old
+ new
@@ -156,28 +156,62 @@
_check_with_label(:checkbox, false, locator, **options)
end
##
#
- # If `:from` option is present, `select` finds a select box on the page
- # and selects a particular option from it.
+ # If `:from` option is present, `select` finds a select box, or text input with associated datalist,
+ # on the page and selects a particular option from it.
# Otherwise it finds an option inside current scope and selects it.
# If the select box is a multiple select, +select+ can be called multiple times to select more than
# one option.
# The select box can be found via its name, id or label text. The option can be found by its text.
#
# page.select 'March', from: 'Month'
#
# @macro waiting_behavior
#
- # @param [String] value Which option to select
+ # @param [String] value Which option to select
# @option options [String] :from The id, name or label of the select box
#
# @return [Capybara::Node::Element] The option element selected
def select(value = nil, from: nil, **options)
- scope = from ? find(:select, from, options) : self
- scope.find(:option, value, options).select_option
+ scope = if from
+ synchronize(Capybara::Queries::BaseQuery.wait(options, session_options.default_max_wait_time)) do
+ begin
+ find(:select, from, options)
+ rescue Capybara::ElementNotFound => select_error
+ raise if %i[selected with_selected multiple].any? { |option| options.key?(option) }
+ begin
+ find(:datalist_input, from, options)
+ rescue Capybara::ElementNotFound => dlinput_error
+ raise Capybara::ElementNotFound, "#{select_error.message} and #{dlinput_error.message}"
+ end
+ end
+ end
+ else
+ self
+ end
+
+ if scope.respond_to?(:tag_name) && scope.tag_name == "input"
+ begin
+ # TODO: this is a more efficient but won't work with non-JS drivers
+ # datalist_options = session.evaluate_script('Array.prototype.slice.call((arguments[0].list||{}).options || []).filter(function(el){ return !el.disabled }).map(function(el){ return { "value": el.value, "label": el.label} })', scope)
+ datalist_options = session.evaluate_script(DATALIST_OPTIONS_SCRIPT, scope)
+ if (option = datalist_options.find { |o| o['value'] == value || o['label'] == value })
+ scope.set(option["value"])
+ else
+ raise ::Capybara::ElementNotFound, "Unable to find datalist option \"#{value}\""
+ end
+ rescue ::Capybara::NotSupportedByDriverError
+ # Implement for drivers that don't support JS
+ datalist = find(:xpath, XPath.descendant(:datalist)[XPath.attr(:id) == scope[:list]], visible: false)
+ option = datalist.find(:datalist_option, value, disabled: false)
+ scope.set(option.value)
+ end
+ else
+ scope.find(:option, value, options).select_option
+ end
end
##
#
# Find a select box on the page and unselect a particular option from it. If the select
@@ -288,9 +322,15 @@
var el = arguments[0];
if (el.hasOwnProperty('capybara_style_cache')) {
el.style.cssText = el.capybara_style_cache;
delete el.capybara_style_cache;
}
+ JS
+
+ DATALIST_OPTIONS_SCRIPT = <<-'JS'.freeze
+ Array.prototype.slice.call((arguments[0].list||{}).options || []).
+ filter(function(el){ return !el.disabled }).
+ map(function(el){ return { "value": el.value, "label": el.label} })
JS
end
end
end