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