Search code examples
rubygwtwatirpage-object-gem

How to define custom locating strategy for select


I looking for a proper way to redefine/extend locating strategy for select tag in Gwt app. From html snippet you can see that select tag is not visible. So to select option from list I need to click on button tag, and than select needed li tag from dropdown.

<div class="form-group">
  <select class="bootstrap-select form-control" style="display: none; locator='gender">
    <div class="btn-group">
    <button class="dropdown-toggle" type="button" title="Male">
      <div class="dropdown-menu open">
        <ul class="dropdown-menu inner selectpicker" role="menu">
          <li data-original-index="1"> (contains a>span with option text)
            .....more options
      </ul>
    </div>
  </div>
</div>

I see dirty solution: to implement method in BasePage class. This approach nice page_object sugar(options,get value, etc):

def set_nationality(country, nationality='Nationality')
  select = button_element(xpath: "//button[@title='#{nationality}']")
  select.click
  option = span_element(xpath: "//span[.='#{country}']")
  option.when_visible
  option.click
end

Is there any other more clear way to do so? Using `PageObject::Widgets maybe?

UPD: Here what I expect to get:

def bool_list(name, identifier={:index => 0}, &block)
  define_method("#{name}_btn_element") do
    platform.send('button_for', identifier.clone + "//button")
  end
  define_method("#{name}?") do
    platform.send('button_for', identifier.clone + "//button").exists?
  end
  define_method(name) do
    return platform.select_list_value_for identifier.clone + '/select' unless block_given?
    self.send("#{name}_element").value
  end
  define_method("#{name}=") do |value|
    return platform.select_list_value_set(identifier.clone + '/select', value) unless block_given?
    self.send("#{name}_element").select(value)
  end
  define_method("#{name}_options") do
    element = self.send("#{name}_element")
    (element && element.options) ? element.options.collect(&:text) : []
  end
end 

Solution

  • The select list appears to have the most identify attributes, therefore I would use it as the base element of the widget. All of the other elements, ie the button and list items, would need to be located with respect to the select list. In this case, they all share the same div.form-group ancestor.

    The widget could be defined as:

    class BoolList < PageObject::Elements::SelectList
      def select(value)
        dropdown_toggle_element.click
        option = span_element(xpath: "./..//span[.='#{value}']")
        option.when_visible
        option.click
      end
    
      def dropdown_toggle_element
        button_element(xpath: './../div/button')
      end
    
      def self.accessor_methods(widget, name)
        widget.send('define_method', "#{name}_btn_element") do
          self.send("#{name}_element").dropdown_toggle_element
        end
    
        widget.send('define_method', "#{name}?") do
          self.send("#{name}_btn_element").exists?
        end
    
        widget.send('define_method', name) do
          self.send("#{name}_element").value
        end
    
        widget.send('define_method', "#{name}=") do |value|
          self.send("#{name}_element").select(value)
        end
    
        widget.send('define_method', "#{name}_options") do
          # Since the element is not displayed, we need to check the inner HTML
          element = self.send("#{name}_element")
          (element && element.options) ? element.options.map { |o| o.element.inner_html } : []
        end
      end
    end
    PageObject.register_widget :bool_list, BoolList, :select
    

    Notice that all locators are in relation to the select list. As well, notice that we use the accessor_methods to add the extra methods to the page object.

    The page object would then use the bool_list accessor method. Note that the identifier is for locating the select element, which we said would be the base element of the widget.

    class MyPage
      include PageObject
    
      bool_list(:gender, title: 'Gender')
      bool_list(:nationality, title: 'Nationality')
    end
    

    The page will now be able to call the following methods:

    page.gender_btn_element.click
    page.gender_btn_element.exists?
    page.gender
    page.gender = 'Female'
    page.gender_options
    
    page.nationality_btn_element.click
    page.nationality_btn_element.exists?
    page.nationality
    page.nationality = 'Barbados'
    page.nationality_options