module FireWatir
# Base class for html elements.
# This is not a class that users would normally access.
class Element
include FireWatir::Container
# Number of spaces that separate the property from the value in the to_s method
TO_S_SIZE = 14
# How to get the nodes using XPath in mozilla.
ORDERED_NODE_ITERATOR_TYPE = 5
# To get the number of nodes returned by the xpath expression
NUMBER_TYPE = 1
# To get single node value
FIRST_ORDERED_NODE_TYPE = 9
# This stores the level to which we have gone finding element inside another element.
# This is just to make sure that every element has unique name in JSSH.
@@current_level = 0
# This stores the name of the element that is about to trigger an Javascript pop up.
#@@current_js_object = nil
attr_accessor :element_name
#
# Description:
# Creates new instance of element. If argument is not nil and is of type string this
# sets the element_name and element_type property of the object. These properties can
# be accessed using element_object and element_type methods respectively.
#
# Used internally by FireWatir.
#
# Input:
# element - Name of the variable with which the element is referenced in JSSh.
#
def initialize(element, container=nil)
@container = container
@element_name = element
@element_type = element_type
#puts "in initialize "
#puts caller(0)
#if(element != nil && element.class == String)
#@element_name = element
#elsif(element != nil && element.class == Element)
# @o = element
#end
#puts "@element_name is #{@element_name}"
#puts "@element_type is #{@element_type}"
end
private
def self.def_wrap(ruby_method_name, ole_method_name = nil)
ole_method_name = ruby_method_name unless ole_method_name
class_eval "def #{ruby_method_name}
assert_exists
# Every element has its name starting from element. If yes then
# use element_name to send the command to jssh. Else its a number
# and we are still searching for element, in this case use doc.all
# array with element_name as index to send command to jssh
#puts element_object.to_s
#if(@element_type == 'HTMLDivElement')
# ole_method_name = 'innerHTML'
#end
jssh_socket.send('typeof(' + element_object + '.#{ole_method_name});\n', 0)
return_type = read_socket()
return_value = get_attribute_value(\"#{ole_method_name}\")
#if(return_value == '' || return_value == \"null\")
# return_value = \"\"
#end
if(return_type == \"boolean\")
return_value = false if return_value == \"false\"
return_value = true if return_value == \"true\"
end
#puts return_value
@@current_level = 0
return return_value
end"
end
def get_attribute_value(attribute_name)
#if the attribut name is columnLength get number of cells in first row if rows exist.
case attribute_name
when "columnLength"
rowsLength = js_eval_method "columns"
if (rowsLength != 0 || rowsLength != "")
return js_eval_method("rows[0].cells.length")
end
when "text"
return text
when "url", "href", "src", "action", "name"
return_value = js_eval_method("getAttribute(\"#{attribute_name}\")")
else
jssh_command = "var attribute = '';
if(#{element_object}.#{attribute_name} != undefined)
attribute = #{element_object}.#{attribute_name};
else
attribute = #{element_object}.getAttribute(\"#{attribute_name}\");
attribute;"
return_value = js_eval(jssh_command)
end
if attribute_name == "value"
tagName = js_eval_method("tagName").downcase
type = js_eval_method("type").downcase
if tagName == "button" || ["image", "submit", "reset", "button"].include?(type)
if return_value == "" || return_value == "null"
return_value = js_eval_method "innerHTML"
end
end
end
if return_value == "null" || return_value =~ /\[object\s.*\]/
return_value = ""
end
return return_value
end
private :get_attribute_value
#
# Description:
# Returns an array of the properties of an element, in a format to be used by the to_s method.
# additional attributes are returned based on the supplied atributes hash.
# name, type, id, value and disabled attributes are common to all the elements.
# This method is used internally by to_s method.
#
# Output:
# Array with values of the following properties:
# name, type, id, value disabled and the supplied attribues list.
#
def string_creator(attributes = nil)
n = []
n << "name:".ljust(TO_S_SIZE) + get_attribute_value("name")
n << "type:".ljust(TO_S_SIZE) + get_attribute_value("type")
n << "id:".ljust(TO_S_SIZE) + get_attribute_value("id")
n << "value:".ljust(TO_S_SIZE) + get_attribute_value("value")
n << "disabled:".ljust(TO_S_SIZE) + get_attribute_value("disabled")
#n << "style:".ljust(TO_S_SIZE) + get_attribute_value("style")
#n << "class:".ljust(TO_S_SIZE) + get_attribute_value("className")
if(attributes != nil)
attributes.each do |key,value|
n << "#{key}:".ljust(TO_S_SIZE) + get_attribute_value(value)
end
end
return n
end
#
# Description:
# Sets and clears the colored highlighting on the currently active element.
#
# Input:
# set_or_clear - this can have following two values
# :set - To set the color of the element.
# :clear - To clear the color of the element.
#
def highlight(set_or_clear)
if set_or_clear == :set
#puts "element_name is : #{element_object}"
jssh_command = " var original_color = #{element_object}.style.background;"
jssh_command << " #{element_object}.style.background = \"#{DEFAULT_HIGHLIGHT_COLOR}\"; original_color;"
# TODO: Need to change this so that it would work if user sets any other color.
#puts "color is : #{DEFAULT_HIGHLIGHT_COLOR}"
jssh_socket.send("#{jssh_command}\n", 0)
@original_color = read_socket()
else # BUG: assumes is :clear, but could actually be anything
begin
jssh_socket.send("#{element_object}.style.background = \"#{@original_color}\";\n", 0)
read_socket()
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
protected :highlight
#
# Description:
# Returns array of rows for a given table. Returns nil if calling element is not of table type.
#
# Output:
# Array of row elements in an table or nil
#
def get_rows()
#puts "#{element_object} and #{element_type}"
if(element_type == "HTMLTableElement")
jssh_socket.send("#{element_object}.rows.length;\n", 0)
length = read_socket().to_i
#puts "The number of rows in the table are : #{no_of_rows}"
return_array = Array.new(length)
for i in 0..length - 1 do
return_array[i] = "#{element_object}.rows[#{i}]"
end
return return_array
else
puts "Trying to access rows for Element of type #{element_type}. Element must be of table type to execute this function."
return nil
end
end
private :get_rows
def set_specifier(how, what)
if how.class == Hash and what.nil?
specifiers = how
else
specifiers = {how => what}
end
@specifiers = {:index => 1} # default if not specified
specifiers.each do |how, what|
what = what.to_i if how == :index
how = :href if how == :url
how = :value if how == :caption
how = :class if how == :class_name
@specifiers[how] = what
end
end
#
# Description:
# Locates the element on the page depending upon the parameters passed. Logic for locating the element is written
# in JavaScript and then send to JSSh; so that we don't make small round-trips via socket to JSSh. This is done to
# improve the performance for locating the element.
#
# Input:
# tag - Tag name of the element to be located like "input", "a" etc. This is case insensitive.
# how - The attribute by which you want to locate the element like id, name etc. You can use any attribute-value pair
# that uniquely identifies that element on the page. If there are more that one element that have identical
# attribute-value pair then first element that is found while traversing the DOM will be returned.
# what - The value of the attribute specified by how.
# types - Used if that HTML element to be located has different type like input can be of type image, button etc.
# Default value is nil
# value - This is used only in case of radio buttons where they have same name but different value.
#
# Output:
# Returns nil if unable to locate the element, else return the element.
#
def locate_tagged_element(tag, how, what, types = nil, value = nil)
#puts caller(0)
# how = :value if how == :caption
# how = :href if how == :url
set_specifier(how, what)
#puts "(locate_tagged_element)current element is : #{@container.class} and tag is #{tag}"
# If there is no current element i.e. element in current context we are searching the whole DOM tree.
# So get all the elements.
if(types != nil and types.include?("button"))
jssh_command = "var isButtonElement = true;"
else
jssh_command = "var isButtonElement = false;"
end
# Because in both the below cases we need to get element with respect to document.
# when we locate a frame document is automatically adjusted to point to HTML inside the frame
if(@container.class == FireWatir::Firefox || @container.class == Frame)
#end
#if(@@current_element_object == "")
jssh_command << "var elements_#{tag} = null; elements_#{tag} = #{@container.document_var}.getElementsByTagName(\"#{tag}\");"
if(types != nil and (types.include?("textarea") or types.include?("button")) )
jssh_command << "elements_#{tag} = #{@container.document_var}.body.getElementsByTagName(\"*\");"
end
# @@has_changed = true
else
#puts "container name is: " + @container.element_name
#locate if defined? locate
#@container.locate
jssh_command << "var elements_#{@@current_level}_#{tag} = #{@container.element_name}.getElementsByTagName(\"#{tag}\");"
if(types != nil and (types.include?("textarea") or types.include?("button") ) )
jssh_command << "elements_#{@@current_level}_#{tag} = #{@container.element_name}.getElementsByTagName(\"*\");"
end
# @@has_changed = false
end
if(types != nil)
jssh_command << "var types = new Array("
count = 0
types.each do |type|
if count == 0
jssh_command << "\"#{type}\""
count += 1
else
jssh_command << ",\"#{type}\""
end
end
jssh_command << ");"
else
jssh_command << "var types = null;"
end
#jssh_command << "var elements = #{element_object}.getElementsByTagName('*');"
jssh_command << "var object_index = 1; var o = null; var element_name = \"\";"
case value
when Regexp
jssh_command << "var value = #{ rb_regexp_to_js(value) };"
when nil
jssh_command << "var value = null;"
else
jssh_command << "var value = \"#{value}\";"
end
#add hash arrays
sKey = "var hashKeys = new Array("
sVal = "var hashValues = new Array("
@specifiers.each do |k,v|
sKey += "\"#{k}\","
if v.is_a?(Regexp)
sVal += "#{rb_regexp_to_js(v)},"
else
sVal += "\"#{v}\","
end
end
sKey = sKey[0..sKey.length-2]
sVal = sVal[0..sVal.length-2]
jssh_command << sKey + ");"
jssh_command << sVal + ");"
#index
jssh_command << "var target_index = 1;
for(var k=0; kSign In, in this case value of "value" attribute is "Sign In"
# though value attribute is not supplied. But for Firefox value of "value" attribute is null. So to make sure
# script runs on both IE and Watir we are also considering innerHTML if element is of button type.
jssh_command << " var attribute = \"\";
var same_type = false;
if(types)
{
for(var j=0; j" + result + "