lib/watir-classic/element.rb in watir-classic-3.3.0 vs lib/watir-classic/element.rb in watir-classic-3.4.0
- old
+ new
@@ -8,11 +8,40 @@
include Container # presumes @container is defined
include DragAndDropHelper
attr_accessor :container
+ class << self
+
+ private
+
+ # @!macro attr_ole
+ # @!method $1
+ # Retrieve element's $1 from the $2 OLE method.
+ # @see http://msdn.microsoft.com/en-us/library/hh773183(v=vs.85).aspx MSDN Documentation
+ # @return [String, Boolean, Fixnum] element's "$1" attribute value.
+ # Return type depends of the attribute type.
+ # @return [String] an empty String if the "$1" attribute does not exist.
+ # @macro exists
+ def attr_ole(method_name, ole_method_name=nil)
+ class_eval %Q[
+ def #{method_name}
+ assert_exists
+ ole_method_name = '#{ole_method_name || method_name.to_s.gsub(/\?$/, '')}'
+ ole_object.invoke(ole_method_name) rescue attribute_value(ole_method_name) || '' rescue ''
+ end]
+ end
+ end
+
+ attr_ole :id
+ attr_ole :title
+ attr_ole :class_name, :className
+ attr_ole :unique_number, :uniqueNumber
+ attr_ole :html, :outerHTML
+
# number of spaces that separate the property from the value in the to_s method
+ # @private
TO_S_SIZE = 14
def initialize(container, specifiers)
set_container container
raise ArgumentError, "#{specifiers.inspect} has to be Hash" unless specifiers.is_a?(Hash)
@@ -27,70 +56,37 @@
ole_object.sourceindex <=> other.ole_object.sourceindex
end
alias_method :eql?, :==
- def locate
- @o = @container.locator_for(TaggedElementLocator, @specifiers, self.class).locate
- end
+ # @return [WIN32OLE] OLE object of the element, allowing any methods of the DOM
+ # that Watir doesn't support to be used.
+ def ole_object
+ @o
+ end
- # Return the ole object, allowing any methods of the DOM that Watir doesn't support to be used.
- def ole_object
- @o
- end
-
- def ole_object=(o)
- @o = o
- end
-
def inspect
'#<%s:0x%x located=%s specifiers=%s>' % [self.class, hash*2, !!ole_object, @specifiers.inspect]
end
- private
-
- def self.attr_ole(method_name, ole_method_name=nil)
- class_eval %Q[
- def #{method_name}
- assert_exists
- ole_method_name = '#{ole_method_name || method_name.to_s.gsub(/\?$/, '')}'
- ole_object.invoke(ole_method_name) rescue attribute_value(ole_method_name) || '' rescue ''
- end]
+ def to_s
+ assert_exists
+ string_creator.join("\n")
end
- public
-
- def assert_exists
- locate
- unless ole_object
- exception_class = self.is_a?(Frame) ? UnknownFrameException : UnknownObjectException
- raise exception_class.new(Watir::Exception.message_for_unable_to_locate(@specifiers))
- end
- end
-
- def assert_enabled
- raise ObjectDisabledException, "object #{@specifiers.inspect} is disabled" unless enabled?
- end
-
- # return the id of the element
- attr_ole :id
- # return the title of the element
- attr_ole :title
- # return the class name of the element
- # raise an ObjectNotFound exception if the object cannot be found
- attr_ole :class_name, :className
- # return the unique COM number for the element
- attr_ole :unique_number, :uniqueNumber
- # Return the outer html of the object - see http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/outerhtml.asp?frame=true
- attr_ole :html, :outerHTML
-
+ # @return [String] element's html tag name in downcase.
+ # @macro exists
def tag_name
assert_exists
@o.tagName.downcase
end
- # returns specific Element subclass for current Element
+ # Cast {Element} into specific subclass.
+ # @example Convert div element to {Div} class:
+ # browser.element(:tag_name => "div").to_subtype # => Watir::Div
+ # @return {Element} element casted into specific sub-class of Element.
+ # @macro exists
def to_subtype
assert_exists
tag = tag_name
if tag == "html"
@@ -104,265 +100,126 @@
else
self
end
end
- # send keys to element
+ # Send keys to the element
+ # @example
+ # browser.text_field.send_keys "hello", [:control, "a"], :backspace
+ # @param [String, Array<Symbol, String>, Symbol] keys Keys to send to the element.
+ # @see https://github.com/jarmo/RAutomation/blob/master/lib/rautomation/adapter/win_32/window.rb RAutomation::Window#send_keys documentation.
def send_keys(*keys)
focus
page_container.send_keys *keys
end
- # return the css style as a string
+ # Retrieve element's css style.
+ # @param [String] property When property is specified then only css for that property is returned.
+ # @return [String] css style as a one long String.
+ # @return [String] css style for specified property if property parameter is specified.
+ # @macro exists
def style(property=nil)
assert_exists
css = ole_object.style.cssText
if property
- properties = Hash[css.downcase.split(";").map { |p| p.split(":").map(&:strip) }]
- properties[property]
+ properties = Hash[css.downcase.split(";").map { |p| p.split(":").map(&:strip) }]
+ properties[property]
else
css
end
end
- # Return the innerText of the object or an empty string if the object is
- # not visible
- # Raise an ObjectNotFound exception if the object cannot be found
+ # The text value of the element between html tags.
+ # @return [String] element's text.
+ # @return [String] empty String when element is not visible.
+ # @macro exists
def text
assert_exists
visible? ? ole_object.innerText.strip : ""
end
- def __ole_inner_elements
- assert_exists
- ole_object.all
- end
-
- def document
- assert_exists
- ole_object
- end
-
- # Return the element immediately containing self.
+ # Retrieve the element immediately containing self.
+ # @return [Element] parent element of self.
+ # @return [Element] self when parent element does not exist.
+ # @macro exists
def parent
assert_exists
parent_element = ole_object.parentelement
return unless parent_element
Element.new(self, :ole_object => parent_element).to_subtype
end
- def typingspeed
- @container.typingspeed
- end
-
- def type_keys
- @type_keys || @container.type_keys
- end
-
- def activeObjectHighLightColor
- @container.activeObjectHighLightColor
- end
-
- # Return an array with many of the properties, in a format to be used by the to_s method
- def string_creator
- n = []
- n << "id:".ljust(TO_S_SIZE) + self.id.to_s
- return n
- end
-
- private :string_creator
-
- # Display basic details about the object. Sample output for a button is shown.
- # Raises UnknownObjectException if the object is not found.
- # name b4
- # type button
- # id b5
- # value Disabled Button
- # disabled true
- def to_s
- assert_exists
- return string_creator.join("\n")
- end
-
- # This method is responsible for setting and clearing the colored highlighting on the currently active element.
- # use :set to set the highlight
- # :clear to clear the highlight
- # TODO: Make this two methods: set_highlight & clear_highlight
- # TODO: Remove begin/rescue blocks
- def highlight(set_or_clear)
- if set_or_clear == :set
- begin
- @original_color ||= ole_object.style.backgroundColor
- ole_object.style.backgroundColor = @container.activeObjectHighLightColor
- rescue
- @original_color = nil
- end
- else
- begin
- ole_object.style.backgroundColor = @original_color if @original_color
- rescue
- # we could be here for a number of reasons...
- # e.g. page may have reloaded and the reference is no longer valid
- ensure
- @original_color = nil
- end
- end
- end
-
- private :highlight
-
- # This method clicks the active element.
- # raises: UnknownObjectException if the object is not found
- # ObjectDisabledException if the object is currently disabled
+ # Performs a left click on the element.
+ # Will wait automatically until browser is ready after the click if page load was triggered for example.
+ # @macro exists
+ # @macro enabled
def click
click!
@container.wait
end
+ # Performs a right click on the element.
+ # Will wait automatically until browser is ready after the click if page load was triggered for example.
+ # @macro enabled
+ # @macro exists
def right_click
perform_action {fire_event("oncontextmenu"); @container.wait}
end
+ # Performs a double click on the element.
+ # Will wait automatically until browser is ready after the click if page load was triggered for example.
+ # @macro exists
+ # @macro enabled
def double_click
perform_action {fire_event("ondblclick"); @container.wait}
end
- def replace_method(method)
- method == 'click' ? 'click!' : method
- end
-
- private :replace_method
-
- def build_method(method_name, *args)
- arguments = args.map do |argument|
- if argument.is_a?(String)
- argument = "'#{argument}'"
- else
- argument = argument.inspect
- end
- end
- "#{replace_method(method_name)}(#{arguments.join(',')})"
- end
-
- private :build_method
-
- def generate_ruby_code(element, method_name, *args)
- # needs to be done like this to avoid segfault on ruby 1.9.3
- tag_name = @specifiers[:tag_name].join("' << '")
- element = "#{self.class}.new(#{@page_container.attach_command}, :tag_name => Array.new << '#{tag_name}', :unique_number => #{unique_number})"
- method = build_method(method_name, *args)
- ruby_code = "$:.unshift(#{$LOAD_PATH.map {|p| "'#{p}'" }.join(").unshift(")});" <<
- "require '#{File.expand_path(File.dirname(__FILE__))}/core';#{element}.#{method};"
- ruby_code
- end
-
- private :generate_ruby_code
-
- def spawned_no_wait_command(command)
- command = "-e #{command.inspect}"
- unless $DEBUG
- "start rubyw #{command}"
- else
- puts "#no_wait command:"
- command = "ruby #{command}"
- puts command
- command
- end
- end
-
- private :spawned_no_wait_command
-
- def click!
- perform_action do
- # Not sure why but in IE9 Document mode, passing a parameter
- # to click seems to work. Firing the onClick event breaks other tests
- # so this seems to be the safest change and also works fine in IE8
- ole_object.click(0)
- end
- end
-
- # Flash the element the specified number of times.
- # Defaults to 10 flashes.
- def flash number=10
+ # Flash the element the specified number of times for troubleshooting purposes.
+ # @param [Fixnum] number Number times to flash the element.
+ # @macro exists
+ def flash(number=10)
assert_exists
number.times do
- highlight(:set)
+ set_highlight
sleep 0.05
- highlight(:clear)
+ clear_highlight
sleep 0.05
end
- nil
+ self
end
- # Executes a user defined "fireEvent" for objects with JavaScript events tied to them such as DHTML menus.
- # usage: allows a generic way to fire javascript events on page objects such as "onMouseOver", "onClick", etc.
- # raises: UnknownObjectException if the object is not found
- # ObjectDisabledException if the object is currently disabled
+ # Executes a user defined "fireEvent" for element with JavaScript events.
+ #
+ # @example Fire a onchange event on select_list:
+ # browser.select_list.fire_event "onchange"
+ #
+ # @macro exists
def fire_event(event)
perform_action {dispatch_event(event); @container.wait}
end
- def dispatch_event(event)
- if IE.version_parts.first.to_i >= 9 && container.page_container.document.documentMode.to_i >= 9
- ole_object.dispatchEvent(create_event(event))
- else
- ole_object.fireEvent(event)
- end
- end
-
- def create_event(event)
- event =~ /on(.*)/i
- event = $1 if $1
- event.downcase!
- # See http://www.howtocreate.co.uk/tutorials/javascript/domevents
- case event
- when 'abort', 'blur', 'change', 'error', 'focus', 'load',
- 'reset', 'resize', 'scroll', 'select', 'submit', 'unload'
- event_name = :initEvent
- event_type = 'HTMLEvents'
- event_args = [event, true, true]
- when 'select'
- event_name = :initUIEvent
- event_type = 'UIEvent'
- event_args = [event, true, true, @container.page_container.document.parentWindow.window,0]
- when 'keydown', 'keypress', 'keyup'
- event_name = :initKeyboardEvent
- event_type = 'KeyboardEvent'
- # 'type', bubbles, cancelable, windowObject, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode
- event_args = [event, true, true, @container.page_container.document.parentWindow.window, false, false, false, false, 0, 0]
- when 'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup',
- 'contextmenu', 'drag', 'dragstart', 'dragenter', 'dragover', 'dragleave', 'dragend', 'drop', 'selectstart'
- event_name = :initMouseEvent
- event_type = 'MouseEvents'
- # 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget
- event_args = [event, true, true, @container.page_container.document.parentWindow.window, 1, 0, 0, 0, 0, false, false, false, false, 0, @container.page_container.document]
- else
- raise UnhandledEventException, "Don't know how to trigger event '#{event}'"
- end
- event = @container.page_container.document.createEvent(event_type)
- event.send event_name, *event_args
- event
- end
-
- # This method sets focus on the active element.
- # raises: UnknownObjectException if the object is not found
- # ObjectDisabledException if the object is currently disabled
+ # Set focus on the element.
+ # @macro exists
+ # @macro enabled
def focus
assert_exists
assert_enabled
@container.focus
ole_object.focus(0)
end
+ # @return [Boolean] true when element is in focus, false otherwise.
+ # @macro exists
+ # @macro enabled
def focused?
assert_exists
assert_enabled
@page_container.document.activeElement.uniqueNumber == unique_number
end
- # Returns whether this element actually exists.
+ # @return [Boolean] true when element exists, false otherwise.
def exists?
begin
locate
rescue WIN32OLERuntimeError, UnknownObjectException
@o = nil
@@ -370,26 +227,28 @@
!!@o
end
alias :exist? :exists?
- # Returns true if the element is enabled, false if it isn't.
- # raises: UnknownObjectException if the object is not found
+ # @return [Boolean] true if the element is enabled, false otherwise.
+ # @macro exists
def enabled?
assert_exists
!disabled?
end
+ # @return [Boolean] true if the element is disabled, false otherwise.
+ # @macro exists
def disabled?
assert_exists
false
end
- # If any parent element isn't visible then we cannot write to the
- # element. The only realiable way to determine this is to iterate
- # up the DOM element tree checking every element to make sure it's
- # visible.
+ # Retrieve the status of element's visibility.
+ # When any parent element is not also visible then the current element is determined as not visible too.
+ # @return [Boolean] true if element is visible, false otherwise.
+ # @macro exists
def visible?
# Now iterate up the DOM element tree and return false if any
# parent element isn't visible
assert_exists
object = @o
@@ -407,26 +266,26 @@
end
true
end
# Get attribute value for any attribute of the element.
- # Returns null if attribute doesn't exist.
+ # @return [String] the value of the attribute.
+ # @return [Object] nil if the attribute does not exist.
+ # @macro exists
def attribute_value(attribute_name)
assert_exists
ole_object.getAttribute(attribute_name)
end
- def perform_action
- assert_exists
- assert_enabled
- highlight(:set)
- yield
- highlight(:clear)
- end
-
- private :perform_action
-
+ # Make it possible to use *_no_wait commands and retrieve element html5 data-attribute
+ # values.
+ #
+ # @example Use click without waiting:
+ # browser.button.click_no_wait
+ #
+ # @example Retrieve html5 data attribute value:
+ # browser.div.data_model # => value of data-model="foo" html attribute
def method_missing(method_name, *args, &block)
meth = method_name.to_s
if meth =~ /(.*)_no_wait/ && self.respond_to?($1)
perform_action do
ruby_code = generate_ruby_code(self, $1, *args)
@@ -436,8 +295,184 @@
self.send(:attribute_value, meth.gsub("_", "-")) || ''
else
super
end
end
-
+
+ # @private
+ def locate
+ @o = @container.locator_for(TaggedElementLocator, @specifiers, self.class).locate
+ end
+
+ # @private
+ def __ole_inner_elements
+ assert_exists
+ ole_object.all
+ end
+
+ # @private
+ def document
+ assert_exists
+ ole_object
+ end
+
+ # @private
+ def assert_exists
+ locate
+ unless ole_object
+ exception_class = self.is_a?(Frame) ? UnknownFrameException : UnknownObjectException
+ raise exception_class.new(Watir::Exception.message_for_unable_to_locate(@specifiers))
+ end
+ end
+
+ # @private
+ def assert_enabled
+ raise ObjectDisabledException, "object #{@specifiers.inspect} is disabled" unless enabled?
+ end
+
+ # @private
+ def typingspeed
+ @container.typingspeed
+ end
+
+ # @private
+ def type_keys
+ @type_keys || @container.type_keys
+ end
+
+ # @private
+ def active_object_highlight_color
+ @container.active_object_highlight_color
+ end
+
+ # @private
+ def click!
+ perform_action do
+ # Not sure why but in IE9 Document mode, passing a parameter
+ # to click seems to work. Firing the onClick event breaks other tests
+ # so this seems to be the safest change and also works fine in IE8
+ ole_object.click(0)
+ end
+ end
+
+ # @private
+ def dispatch_event(event)
+ if IE.version_parts.first.to_i >= 9 && container.page_container.document.documentMode.to_i >= 9
+ ole_object.dispatchEvent(create_event(event))
+ else
+ ole_object.fireEvent(event)
+ end
+ end
+
+ private
+
+ def create_event(event)
+ event =~ /on(.*)/i
+ event = $1 if $1
+ event.downcase!
+ # See http://www.howtocreate.co.uk/tutorials/javascript/domevents
+ case event
+ when 'abort', 'blur', 'change', 'error', 'focus', 'load',
+ 'reset', 'resize', 'scroll', 'select', 'submit', 'unload'
+ event_name = :initEvent
+ event_type = 'HTMLEvents'
+ event_args = [event, true, true]
+ when 'select'
+ event_name = :initUIEvent
+ event_type = 'UIEvent'
+ event_args = [event, true, true, @container.page_container.document.parentWindow.window,0]
+ when 'keydown', 'keypress', 'keyup'
+ event_name = :initKeyboardEvent
+ event_type = 'KeyboardEvent'
+ # 'type', bubbles, cancelable, windowObject, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode
+ event_args = [event, true, true, @container.page_container.document.parentWindow.window, false, false, false, false, 0, 0]
+ when 'click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup',
+ 'contextmenu', 'drag', 'dragstart', 'dragenter', 'dragover', 'dragleave', 'dragend', 'drop', 'selectstart'
+ event_name = :initMouseEvent
+ event_type = 'MouseEvents'
+ # 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget
+ event_args = [event, true, true, @container.page_container.document.parentWindow.window, 1, 0, 0, 0, 0, false, false, false, false, 0, @container.page_container.document]
+ else
+ raise UnhandledEventException, "Don't know how to trigger event '#{event}'"
+ end
+ event = @container.page_container.document.createEvent(event_type)
+ event.send event_name, *event_args
+ event
+ end
+
+ # Return an array with many of the properties, in a format to be used by the to_s method
+ def string_creator
+ n = []
+ n << "id:".ljust(TO_S_SIZE) + self.id.to_s
+ n
+ end
+
+ # This method is responsible for setting colored highlighting on the currently active element.
+ def set_highlight
+ perform_highlight do
+ @original_color = ole_object.style.backgroundColor
+ ole_object.style.backgroundColor = @container.active_object_highlight_color
+ end
+ end
+
+ # This method is responsible for clearing colored highlighting on the currently active element.
+ def clear_highlight
+ perform_highlight do
+ ole_object.style.backgroundColor = @original_color if @original_color
+ end
+ end
+
+ def perform_highlight
+ yield
+ rescue
+ # we could be here for a number of reasons...
+ # e.g. page may have reloaded and the reference is no longer valid
+ end
+
+ def replace_method(method)
+ method == 'click' ? 'click!' : method
+ end
+
+ def build_method(method_name, *args)
+ arguments = args.map do |argument|
+ if argument.is_a?(String)
+ argument = "'#{argument}'"
+ else
+ argument = argument.inspect
+ end
+ end
+ "#{replace_method(method_name)}(#{arguments.join(',')})"
+ end
+
+ def generate_ruby_code(element, method_name, *args)
+ # needs to be done like this to avoid segfault on ruby 1.9.3
+ tag_name = @specifiers[:tag_name].join("' << '")
+ element = "#{self.class}.new(#{@page_container.attach_command}, :tag_name => Array.new << '#{tag_name}', :unique_number => #{unique_number})"
+ method = build_method(method_name, *args)
+ ruby_code = "$:.unshift(#{$LOAD_PATH.map {|p| "'#{p}'" }.join(").unshift(")});" <<
+ "require '#{File.expand_path(File.dirname(__FILE__))}/core';#{element}.#{method};"
+ ruby_code
+ end
+
+ def spawned_no_wait_command(command)
+ command = "-e #{command.inspect}"
+ unless $DEBUG
+ "start rubyw #{command}"
+ else
+ puts "#no_wait command:"
+ command = "ruby #{command}"
+ puts command
+ command
+ end
+ end
+
+ def perform_action
+ assert_exists
+ assert_enabled
+ set_highlight
+ yield
+ ensure
+ clear_highlight
+ end
+
end
end