class Watir::Element

Base class for HTML elements.

Constants

CASE_INSENSITIVE_ATTRIBUTES

www.w3.org/TR/html52/single-page.html#casesensitivity

Attributes

keyword[RW]
selector[R]

Public Class Methods

new(query_scope, selector) click to toggle source
# File lib/watir/elements/element.rb, line 43
def initialize(query_scope, selector)
  @query_scope = query_scope

  raise ArgumentError, "invalid argument: #{selector.inspect}" unless selector.is_a? Hash

  @element = selector.delete(:element)

  if @element && !(selector.keys - %i[tag_name]).empty?
    Watir.logger.deprecate(':element locator to initialize a relocatable Element', '#cache=', ids: [:element_cache])
  end

  @selector = selector

  build unless @element
end

Public Instance Methods

==(other) click to toggle source

Returns true if two elements are equal.

TODO: Address how this is affected by stale elements TODO: Address how this is affected by HTMLElement vs subclass TODO: Address how this is affected by a non-located element

@example

browser.text_field(name: "new_user_first_name") == browser.text_field(name: "new_user_first_name")
#=> true
# File lib/watir/elements/element.rb, line 105
def ==(other)
  other.is_a?(self.class) && wd == other.wd
end
Also aliased as: eql?
attribute(attribute_name)
Alias for: attribute_value
attribute_list() click to toggle source

Returns list of all attributes. Attributes with special characters are returned as String, rest are returned as a Symbol.

@return [Array]

@example

browser.pre(id: 'rspec').attribute_list
#=> [:class, :id]
# File lib/watir/elements/element.rb, line 351
def attribute_list
  attribute_values.keys
end
attribute_value(attribute_name) click to toggle source

Returns given attribute value of element.

@example

browser.a(id: "link_2").attribute_value "title"
#=> "link_title_2"

@param [String, ::Symbol] attribute_name @return [String, nil]

# File lib/watir/elements/element.rb, line 312
def attribute_value(attribute_name)
  attribute_name = attribute_name.to_s.tr('_', '-') if attribute_name.is_a?(::Symbol)
  element_call { @element.attribute attribute_name }
end
Also aliased as: attribute
attribute_values() click to toggle source

Returns all attribute values. Attributes with special characters are returned as String, rest are returned as a Symbol.

@return [Hash]

@example

browser.pre(id: 'rspec').attribute_values
#=> {class:'ruby', id: 'rspec' }
# File lib/watir/elements/element.rb, line 329
def attribute_values
  result = element_call { execute_js(:attributeValues, @element) }
  result.keys.each do |key|
    next unless key == key[/[a-zA-Z\-]*/]

    result[key.tr('-', '_').to_sym] = result.delete(key)
  end
  result
end
Also aliased as: attributes
attributes()
Alias for: attribute_values
browser() click to toggle source

Returns browser.

@return [Watir::Browser]

# File lib/watir/elements/element.rb, line 590
def browser
  @query_scope.browser
end
build() click to toggle source

@api private

# File lib/watir/elements/element.rb, line 633
def build
  selector_builder.build(@selector.dup)
end
cache=(element) click to toggle source

@api private

Set the cached element. For use when element can be relocated with the provided selector.

# File lib/watir/elements/element.rb, line 655
def cache=(element)
  @element = element
end
center() click to toggle source

Get centre coordinates of element

@example

browser.button(name: "new_user_button").centre

@return [Selenium::WebDriver::Point]

# File lib/watir/elements/element.rb, line 454
def center
  point = location
  dimensions = size
  Selenium::WebDriver::Point.new(point.x + (dimensions['width'] / 2),
                                 point.y + (dimensions['height'] / 2))
end
Also aliased as: centre
centre()
Alias for: center
classes() click to toggle source

Returns list of class values.

@return [Array]

# File lib/watir/elements/element.rb, line 297
def classes
  class_name.split
end
click(*modifiers) click to toggle source

Clicks the element, optionally while pressing the given modifier keys. Note that support for holding a modifier key is currently experimental, and may not work at all.

@example Click an element

browser.element(name: "new_user_button").click

@example Click an element with shift key pressed

browser.element(name: "new_user_button").click(:shift)

@example Click an element with several modifier keys pressed

browser.element(name: "new_user_button").click(:shift, :control)

@param [:shift, :alt, :control, :command, :meta] modifiers to press while clicking.

# File lib/watir/elements/element.rb, line 151
def click(*modifiers)
  element_call(:wait_for_enabled) do
    if modifiers.any?
      action = driver.action
      modifiers.each { |mod| action.key_down mod }
      action.click @element
      modifiers.each { |mod| action.key_up mod }

      action.perform
    else
      @element.click
    end
  end

  browser.after_hooks.run
end
click!() click to toggle source

Simulates JavaScript click event on element.

@example Click an element

browser.element(name: "new_user_button").click!
# File lib/watir/elements/element.rb, line 175
def click!
  fire_event :click
  browser.after_hooks.run
end
double_click() click to toggle source

Double clicks the element. Note that browser support may vary.

@example

browser.element(name: "new_user_button").double_click
# File lib/watir/elements/element.rb, line 188
def double_click
  element_call(:wait_for_present) { driver.action.double_click(@element).perform }
  browser.after_hooks.run
end
double_click!() click to toggle source

Simulates JavaScript double click event on element.

@example

browser.element(name: "new_user_button").double_click!
# File lib/watir/elements/element.rb, line 200
def double_click!
  fire_event :dblclick
  browser.after_hooks.run
end
drag_and_drop_by(right_by, down_by) click to toggle source

Drag and drop this element by the given offsets. Note that browser support may vary.

@example

browser.div(id: "draggable").drag_and_drop_by 100, 25

@param [Integer] right_by @param [Integer] down_by

# File lib/watir/elements/element.rb, line 283
def drag_and_drop_by(right_by, down_by)
  element_call(:wait_for_present) do
    driver.action
          .drag_and_drop_by(@element, right_by, down_by)
          .perform
  end
end
drag_and_drop_on(other) click to toggle source

Drag and drop this element on to another element instance. Note that browser support may vary.

@example

a = browser.div(id: "draggable")
b = browser.div(id: "droppable")
a.drag_and_drop_on b
# File lib/watir/elements/element.rb, line 260
def drag_and_drop_on(other)
  assert_is_element other

  value = element_call(:wait_for_present) do
    driver.action
          .drag_and_drop(@element, other.wd)
          .perform
  end
  browser.after_hooks.run
  value
end
driver() click to toggle source

@api private

# File lib/watir/elements/element.rb, line 466
def driver
  @query_scope.driver
end
enabled?() click to toggle source

Returns true if this element is present and enabled on the page.

@return [Boolean] @see Watir::Wait

# File lib/watir/elements/element.rb, line 502
def enabled?
  element_call(:assert_exists) { @element.enabled? }
end
eql?(other)
Alias for: ==
exist?()
Alias for: exists?
exists?() click to toggle source

Returns true if element exists. Checking for staleness is deprecated

@return [Boolean]

# File lib/watir/elements/element.rb, line 66
def exists?
  if located? && stale?
    reset!
  elsif located?
    return true
  end

  assert_exists
  true
rescue UnknownObjectException, UnknownFrameException
  false
end
Also aliased as: exist?
focused?() click to toggle source

Returns true if this element is focused.

@return [Boolean]

# File lib/watir/elements/element.rb, line 374
def focused?
  element_call { @element == driver.switch_to.active_element }
end
hash() click to toggle source
Calls superclass method
# File lib/watir/elements/element.rb, line 110
def hash
  located? ? @element.hash : super
end
height() click to toggle source

Get height of element

@example

browser.button(name: "new_user_button").height

@return [Selenium::WebDriver::Dimension]

# File lib/watir/elements/element.rb, line 428
def height
  size['height']
end
hover() click to toggle source

Moves the mouse to the middle of this element. Note that browser support may vary.

@example

browser.element(name: "new_user_button").hover
# File lib/watir/elements/element.rb, line 246
def hover
  element_call(:wait_for_present) { driver.action.move_to(@element).perform }
end
inspect() click to toggle source
# File lib/watir/elements/element.rb, line 80
def inspect
  string = "#<#{self.class}: "
  string << "keyword: #{keyword} " if keyword
  string << "located: #{located?}; "
  string << if @selector.empty?
              '{element: (selenium element)}'
            else
              selector_string
            end
  string << '>'
  string
end
locate() click to toggle source

@api private

# File lib/watir/elements/element.rb, line 623
def locate
  ensure_context
  locate_in_context
  self
end
located?() click to toggle source

@api private

Returns true if element has been previously located.

@return [Boolean]

# File lib/watir/elements/element.rb, line 645
def located?
  !!@element
end
location() click to toggle source

location of element (x, y)

@example

browser.button(name: "new_user_button").location

@return [Selenium::WebDriver::Point]

# File lib/watir/elements/element.rb, line 402
def location
  element_call { @element.location }
end
obscured?() click to toggle source

Returns true if the element's center point is covered by a non-descendant element.

@return [Boolean]

# File lib/watir/elements/element.rb, line 526
def obscured?
  element_call do
    return true unless present?

    scroll.to
    execute_js(:elementObscured, self)
  end
end
present?() click to toggle source

Returns true if the element exists and is visible on the page. Returns false if element does not exist or exists but is not visible

@return [Boolean] @see Watir::Wait

# File lib/watir/elements/element.rb, line 514
def present?
  display_check
rescue UnknownObjectException, UnknownFrameException
  false
end
reset!() click to toggle source
# File lib/watir/elements/element.rb, line 615
def reset!
  @element = nil
end
right_click(*modifiers) click to toggle source

Right clicks the element, optionally while pressing the given modifier keys. Note that support for holding a modifier key is currently experimental, and may not work at all. Also, the browser support may vary.

@example

browser.element(name: "new_user_button").right_click

@example Right click an element with shift key pressed

browser.element(name: "new_user_button").right_click(:shift)

@example Click an element with several modifier keys pressed

browser.element(name: "new_user_button").right_click(:shift, :alt)

@param [:shift, :alt, :control, :command, :meta] modifiers to press while right clicking.

# File lib/watir/elements/element.rb, line 222
def right_click(*modifiers)
  element_call(:wait_for_present) do
    action = driver.action
    if modifiers.any?
      modifiers.each { |mod| action.key_down mod }
      action.context_click(@element)
      modifiers.each { |mod| action.key_up mod }
      action.perform
    else
      action.context_click(@element).perform
    end
  end

  browser.after_hooks.run
end
scroll_into_view() click to toggle source

Scroll until the element is in the view screen

@example

browser.button(name: "new_user_button").scroll_into_view

@return [Selenium::WebDriver::Point]

# File lib/watir/elements/element.rb, line 387
def scroll_into_view
  Watir.logger.deprecate 'Element#scroll_into_view', 'Element#scroll methods', ids: [:scroll_into_view]

  element_call { @element.location_once_scrolled_into_view }
end
selector_string() click to toggle source

@api private

# File lib/watir/elements/element.rb, line 663
def selector_string
  return @selector.inspect if @query_scope.is_a?(Browser)

  "#{@query_scope.selector_string} --> #{@selector.inspect}"
end
send_keys(*args) click to toggle source

Sends sequence of keystrokes to element.

@example

browser.text_field(name: "new_user_first_name").send_keys "Watir", :return

@param [String, Symbol] args

# File lib/watir/elements/element.rb, line 364
def send_keys(*args)
  element_call(:wait_for_writable) { @element.send_keys(*args) }
end
size() click to toggle source

size of element (width, height)

@example

browser.button(name: "new_user_button").size

@return [Selenium::WebDriver::Dimension]

# File lib/watir/elements/element.rb, line 415
def size
  element_call { @element.size }
end
stale?() click to toggle source

Returns true if a previously located element is no longer attached to DOM.

@return [Boolean] @see Watir::Wait

# File lib/watir/elements/element.rb, line 601
def stale?
  raise Error, 'Can not check staleness of unused element' unless @element

  ensure_context
  stale_in_context?
end
stale_in_context?() click to toggle source
# File lib/watir/elements/element.rb, line 608
def stale_in_context?
  @element.css_value('staleness_check') # any wire call will check for staleness
  false
rescue Selenium::WebDriver::Error::StaleElementReferenceError
  true
end
style(property = nil) click to toggle source

Returns given style property of this element.

@example

browser.button(value: "Delete").style           #=> "border: 4px solid red;"
browser.button(value: "Delete").style("border") #=> "4px solid rgb(255, 0, 0)"

@param [String] property @return [String]

# File lib/watir/elements/element.rb, line 546
def style(property = nil)
  if property
    element_call { @element.style property }
  else
    attribute_value('style').to_s.strip
  end
end
tag_name() click to toggle source

Returns tag name of the element.

@return [String]

# File lib/watir/elements/element.rb, line 130
def tag_name
  element_call { @element.tag_name.downcase }
end
text() click to toggle source

Returns the text of the element.

@return [String]

# File lib/watir/elements/element.rb, line 120
def text
  element_call { @element.text }
end
to_subtype() click to toggle source

Cast this Element instance to a more specific subtype.

@example

browser.element(xpath: "//input[@type='submit']").to_subtype
#=> #<Watir::Button>
# File lib/watir/elements/element.rb, line 562
def to_subtype
  tag = tag_name
  klass = if tag == 'input'
            case attribute_value(:type)
            when 'checkbox'
              CheckBox
            when 'radio'
              Radio
            when 'file'
              FileField
            when *Button::VALID_TYPES
              Button
            else
              TextField
            end
          else
            Watir.element_class_for(tag)
          end

  klass.new(@query_scope, @selector).tap { |el| el.cache = wd }
end
visible?() click to toggle source

Returns true if this element is visible on the page. Raises exception if element does not exist

@return [Boolean]

# File lib/watir/elements/element.rb, line 488
def visible?
  msg = '#visible? behavior will be changing slightly, consider switching to #present? ' \
        '(more details: http://watir.com/element-existentialism/)'
  Watir.logger.warn msg, ids: [:visible_element]
  display_check
end
wd() click to toggle source

Returns underlying Selenium object of the Watir Element

@return [Selenium::WebDriver::Element]

# File lib/watir/elements/element.rb, line 476
def wd
  assert_exists
  @element
end
width() click to toggle source

Get width of element

@example

browser.button(name: "new_user_button").width

@return [Selenium::WebDriver::Dimension]

# File lib/watir/elements/element.rb, line 441
def width
  size['width']
end

Protected Instance Methods

assert_exists() click to toggle source

Locates if not previously found; does not check for staleness for performance reasons

# File lib/watir/elements/element.rb, line 726
def assert_exists
  locate unless located?
  return if located?

  raise unknown_exception, "unable to locate element: #{inspect}"
end
ensure_context() click to toggle source
# File lib/watir/elements/element.rb, line 733
def ensure_context
  if @query_scope.is_a?(Browser) || !@query_scope.located? && @query_scope.is_a?(IFrame)
    @query_scope.browser.locate
  elsif @query_scope.located? && @query_scope.stale?
    @query_scope.locate
  end
  @query_scope.switch_to! if @query_scope.is_a?(IFrame)
end
locate_in_context() click to toggle source
# File lib/watir/elements/element.rb, line 742
def locate_in_context
  @element = locator.locate(selector_builder.built)
end
wait_for_enabled() click to toggle source
# File lib/watir/elements/element.rb, line 698
def wait_for_enabled
  return assert_enabled unless Watir.relaxed_locate?

  wait_for_exists
  return unless [Input, Button, Select, Option].any? { |c| is_a? c } || content_editable
  return if enabled?

  begin
    wait_until(&:enabled?)
  rescue Wait::TimeoutError
    raise_disabled
  end
end
wait_for_exists() click to toggle source
# File lib/watir/elements/element.rb, line 671
def wait_for_exists
  return assert_exists unless Watir.relaxed_locate?
  return if located? # Performance shortcut

  begin
    @query_scope.wait_for_exists unless @query_scope.is_a? Browser
    wait_until(element_reset: true, &:exists?)
  rescue Wait::TimeoutError
    msg = "timed out after #{Watir.default_timeout} seconds, waiting for #{inspect} to be located"
    raise unknown_exception, msg
  end
end
wait_for_present() click to toggle source
# File lib/watir/elements/element.rb, line 684
def wait_for_present
  p = present?
  return p if !Watir.relaxed_locate? || p

  begin
    @query_scope.wait_for_present unless @query_scope.is_a? Browser
    wait_until(&:present?)
  rescue Wait::TimeoutError
    msg = "element located, but timed out after #{Watir.default_timeout} seconds, " \
          "waiting for #{inspect} to be present"
    raise unknown_exception, msg
  end
end
wait_for_writable() click to toggle source
# File lib/watir/elements/element.rb, line 712
def wait_for_writable
  wait_for_enabled
  raise_writable unless Watir.relaxed_locate? || (!respond_to?(:readonly?) || !readonly?)

  return if !respond_to?(:readonly?) || !readonly?

  begin
    wait_until { !respond_to?(:readonly?) || !readonly? }
  rescue Wait::TimeoutError
    raise_writable
  end
end

Private Instance Methods

assert_enabled() click to toggle source
# File lib/watir/elements/element.rb, line 774
def assert_enabled
  raise ObjectDisabledException, "object is disabled #{inspect}" unless element_call { @element.enabled? }
end
assert_is_element(obj) click to toggle source
# File lib/watir/elements/element.rb, line 778
def assert_is_element(obj)
  raise TypeError, "expected Watir::Element, got #{obj.inspect}:#{obj.class}" unless obj.is_a? Element
end
check_condition(condition, caller) click to toggle source

rubocop:enable Metrics/AbcSize rubocop:enable Metrics/MethodLength rubocop:enable Metrics/CyclomaticComplexity rubocop:enable Metrics/PerceivedComplexity

# File lib/watir/elements/element.rb, line 835
def check_condition(condition, caller)
  Watir.logger.debug "<- `Verifying precondition #{inspect}##{condition} for #{caller}`"
  begin
    condition.nil? ? assert_exists : send(condition)
    Watir.logger.debug "<- `Verified precondition #{inspect}##{condition || 'assert_exists'}`"
  rescue unknown_exception
    raise unless condition.nil?

    Watir.logger.debug "<- `Unable to satisfy precondition #{inspect}##{condition}`"
    check_condition(:wait_for_exists, caller)
  end
end
display_check() click to toggle source

Removes duplication in present? & visible? and makes setting deprecation notice easier

# File lib/watir/elements/element.rb, line 783
def display_check
  assert_exists
  @element.displayed?
rescue Selenium::WebDriver::Error::StaleElementReferenceError
  reset!
  retry
end
element_call(precondition = nil) { || ... } click to toggle source

TODO: - this will get addressed with Watir::Executor implementation rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength rubocop:disable Metrics/CyclomaticComplexity:

rubocop:disable Metrics/PerceivedComplexity
# File lib/watir/elements/element.rb, line 796
def element_call(precondition = nil, &block)
  caller = caller_locations(1, 1)[0].label
  already_locked = browser.timer.locked?
  browser.timer = Wait::Timer.new(timeout: Watir.default_timeout) unless already_locked

  begin
    check_condition(precondition, caller)
    Watir.logger.debug "-> `Executing #{inspect}##{caller}`"
    yield
  rescue unknown_exception => e
    element_call(:wait_for_exists, &block) if precondition.nil?
    msg = e.message
    msg += '; Maybe look in an iframe?' if @query_scope.iframe.exists?
    custom_attributes = !defined?(@locator) || @locator.nil? ? [] : selector_builder.custom_attributes
    unless custom_attributes.empty?
      msg += "; Watir treated #{custom_attributes} as a non-HTML compliant attribute, ensure that was intended"
    end
    raise unknown_exception, msg
  rescue Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::NoSuchElementError
    reset!
    retry
    # TODO: - InvalidElementStateError is deprecated, so no longer calling `raise_disabled`
    # need a better way to handle this
  rescue Selenium::WebDriver::Error::ElementNotInteractableError
    raise_present unless browser.timer.remaining_time.positive?
    raise_present unless %i[wait_for_present wait_for_enabled wait_for_writable].include?(precondition)
    retry
  rescue Selenium::WebDriver::Error::NoSuchWindowError
    raise NoMatchingWindowFoundException, 'browser window was closed'
  ensure
    Watir.logger.debug "<- `Completed #{inspect}##{caller}`"
    browser.timer.reset! unless already_locked
  end
end
element_class() click to toggle source
# File lib/watir/elements/element.rb, line 770
def element_class
  self.class
end
method_missing(meth, *args, &blk) click to toggle source
Calls superclass method
# File lib/watir/elements/element.rb, line 848
def method_missing(meth, *args, &blk)
  method = meth.to_s
  if method =~ Locators::Element::SelectorBuilder::WILDCARD_ATTRIBUTE
    attribute_value(meth, *args)
  elsif UserEditable.instance_methods(false).include?(meth) && content_editable?
    @content_editable = true
    extend UserEditable
    send(meth, *args, &blk)
  else
    super
  end
end
raise_disabled() click to toggle source
# File lib/watir/elements/element.rb, line 758
def raise_disabled
  message = "element present, but timed out after #{Watir.default_timeout} seconds, " \
            "waiting for #{inspect} to be enabled"
  raise ObjectDisabledException, message
end
raise_present() click to toggle source
# File lib/watir/elements/element.rb, line 764
def raise_present
  message = "element located, but timed out after #{Watir.default_timeout} seconds, " \
                           "waiting for #{inspect} to be present"
  raise unknown_exception, message
end
raise_writable() click to toggle source
# File lib/watir/elements/element.rb, line 752
def raise_writable
  message = "element present and enabled, but timed out after #{Watir.default_timeout} seconds, " \
            "waiting for #{inspect} to not be readonly"
  raise ObjectReadOnlyException, message
end
respond_to_missing?(meth, *) click to toggle source
Calls superclass method
# File lib/watir/elements/element.rb, line 861
def respond_to_missing?(meth, *)
  meth.to_s =~ Locators::Element::SelectorBuilder::WILDCARD_ATTRIBUTE || super
end
unknown_exception() click to toggle source
# File lib/watir/elements/element.rb, line 748
def unknown_exception
  UnknownObjectException
end