Module: Vapir::Element

Extended by:
ElementHelper
Includes:
Configurable, ElementObjectCandidates
Defined in:
lib/vapir-common/element.rb

Overview

this is included by every Element. it relies on the including class implementing a #element_object method some stuff assumes the element has a defined @container.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ElementHelper

add_specifier, container_collection_method, container_single_method, included

Methods included from ElementClassAndModuleMethods

#add_container_method_extra_args, #all_dom_attr_aliases, #all_dom_attrs, #class_array_append, #class_array_get, #class_hash_get, #class_hash_merge, #container_collection_methods, #container_method_extra_args, #container_single_methods, #default_how, #dom_attr, #dom_attr_locate_alias, #dom_function, #dom_setter, #element_collection, #factory, #inspect_these, #inspect_this_if, #parent_element_module, #set_or_get_class_var, #specifiers

Methods included from Configurable

#config, #with_config

Instance Attribute Details

#browserObject (readonly)

Returns the value of attribute browser.



582
583
584
# File 'lib/vapir-common/element.rb', line 582

def browser
  @browser
end

#howObject (readonly)

Returns the value of attribute how.



107
108
109
# File 'lib/vapir-common/element.rb', line 107

def how
  @how
end

#indexObject (readonly)

Returns the value of attribute index.



109
110
111
# File 'lib/vapir-common/element.rb', line 109

def index
  @index
end

#page_containerObject (readonly)

Returns the value of attribute page_container.



583
584
585
# File 'lib/vapir-common/element.rb', line 583

def page_container
  @page_container
end

#whatObject (readonly)

Returns the value of attribute what.



108
109
110
# File 'lib/vapir-common/element.rb', line 108

def what
  @what
end

Class Method Details

.object_collection_to_enumerable(object) ⇒ Object



647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/vapir-common/element.rb', line 647

def object_collection_to_enumerable(object)
  if object.is_a?(Enumerable)
    object
  elsif Object.const_defined?('JsshObject') && object.is_a?(JsshObject)
    object.to_array
  elsif Object.const_defined?('WIN32OLE') && object.is_a?(WIN32OLE)
    array=[]
    length = object.length
    (0...length).each do |i|
      begin
        array << object.item(i)
      rescue WIN32OLERuntimeError
        # not rescuing, just adding information
        raise $!.class, "accessing item #{i} of #{length}, encountered:\n"+$!.message, $!.backtrace
      end
    end
    array
  else
    raise TypeError, "Don't know how to make enumerable from given object #{object.inspect} (#{object.class})"
  end
end

Instance Method Details

#attr(attribute) ⇒ Object

method to access dom attributes by defined aliases. unlike get_attribute, this only looks at the specific dom attributes that Watir knows about, and the aliases for those that Watir defines.



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/vapir-common/element.rb', line 288

def attr(attribute)
  unless attribute.is_a?(String) || attribute.is_a?(Symbol)
    raise TypeError, "attribute should be string or symbol; got #{attribute.inspect}"
  end
  attribute=attribute.to_sym
  all_aliases=self.class.all_dom_attr_aliases
  dom_attrs=all_aliases.reject{|dom_attr, attr_aliases| !attr_aliases.include?(attribute) }.keys
  case dom_attrs.size
  when 0
    raise ArgumentError, "Not a recognized attribute: #{attribute}"
  when 1
    method_from_element_object(dom_attrs.first)
  else
    raise ArgumentError, "Ambiguously aliased attribute #{attribute} may refer to any of: #{dom_attrs.join(', ')}"
  end
end

#attributes_for_stringifyingObject



598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
# File 'lib/vapir-common/element.rb', line 598

def attributes_for_stringifying
  attributes_to_inspect=self.class.attributes_to_inspect
  unless exists?
    attributes_to_inspect=[{:value => :exists?, :label => :exists?}]+attributes_to_inspect.select{|inspect_hash| [:how, :what, :index].include?(inspect_hash[:label]) }
  end
  attributes_to_inspect.map do |inspect_hash|
    if !inspect_hash[:if] || inspect_hash[:if].call(self)
      value=case inspect_hash[:value]
      when /\A@/ # starts with @, look for instance variable
        instance_variable_get(inspect_hash[:value]).inspect
      when Symbol
        send(inspect_hash[:value])
      when Proc
        inspect_hash[:value].call(self)
      else
        inspect_hash[:value]
      end
      [inspect_hash[:label].to_s, value]
    end
  end.compact
end

#browser_window_objectObject



593
594
595
596
# File 'lib/vapir-common/element.rb', line 593

def browser_window_object
  assert_container
  @container.browser_window_object
end

#client_centerObject

returns a two-element Vector with the position of the center of this element on the client area. intended to be used with mouse events’ clientX and clientY. developer.mozilla.org/en/DOM/event.clientX developer.mozilla.org/en/DOM/event.clientY



525
526
527
# File 'lib/vapir-common/element.rb', line 525

def client_center
  client_offset+dimensions.map{|dim| dim/2}
end

#client_offsetObject

returns a two-element Vector containing the offset of this element on the client area. see also #client_center



516
517
518
# File 'lib/vapir-common/element.rb', line 516

def client_offset
  document_offset-scroll_offset
end

#configuration_parentObject



13
14
15
# File 'lib/vapir-common/element.rb', line 13

def configuration_parent
  @container ? @container.config : @browser ? @browser.config : browser_class.config
end

#containerObject



577
578
579
580
# File 'lib/vapir-common/element.rb', line 577

def container
  assert_container
  @container
end

#content_window_objectObject



589
590
591
592
# File 'lib/vapir-common/element.rb', line 589

def content_window_object
  assert_container
  @container.content_window_object
end

#default_initialize(how, what, extra = {}) ⇒ Object Also known as: initialize

the class-specific Elements may implement their own #initialize, but should call to this after they’ve done their stuff

Raises:

  • (ArgumentError)


121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/vapir-common/element.rb', line 121

def default_initialize(how, what, extra={})
  @how, @what=how, what
  raise ArgumentError, "how (first argument) should be a Symbol, not: #{how.inspect}" unless how.is_a?(Symbol) || how==nil
  @extra=extra
  @index=begin
    valid_symbols=[:first, :last]
    if valid_symbols.include?(@extra[:index]) || @extra[:index].nil? || (@extra[:index].is_a?(Integer) && @extra[:index] > 0)
      @extra[:index]
    elsif valid_symbols.map{|sym| sym.to_s}.include?(@extra[:index])
      @extra[:index].to_sym
    elsif @extra[:index] =~ /\A\d+\z/
      Integer(@extra[:index])
    else
      raise ArgumentError, "expected extra[:index] to be a positive integer, a string that looks like a positive integer, :first, or :last. received #{@extra[:index]} (#{@extra[:index].class})"
    end
  end
  @container=extra[:container]
  @browser=extra[:browser]
  @page_container=extra[:page_container]
  @element_object=extra[:element_object] # this will in most cases not be set, but may be set in some cases from ElementCollection enumeration 
  extra[:locate]=true unless @extra.key?(:locate) # set default 
  case extra[:locate]
  when :assert
    locate!
  when true
    locate
  when false
  else
    raise ArgumentError, "Unrecognized value given for extra[:locate]: #{extra[:locate].inspect} (#{extra[:locate].class})"
  end
end

#dimensionsObject

returns a two-element Vector with the width and height of this element.



563
564
565
# File 'lib/vapir-common/element.rb', line 563

def dimensions
  Vector[element_object.offsetWidth, element_object.offsetHeight]
end

#document_centerObject

returns a two-element Vector with the position of the center of this element on the document.



568
569
570
# File 'lib/vapir-common/element.rb', line 568

def document_center
  document_offset+dimensions.map{|dim| dim/2}
end

#document_objectObject



585
586
587
588
# File 'lib/vapir-common/element.rb', line 585

def document_object
  assert_container
  @container.document_object
end

#document_offsetObject

returns a Vector with two elements, the x,y coordinates of this element (its top left point) from the top left edge of the window



504
505
506
507
508
509
510
511
512
# File 'lib/vapir-common/element.rb', line 504

def document_offset
  xy=Vector[0,0]
  el=element_object
  begin
    xy+=Vector[el.offsetLeft, el.offsetTop]
    el=el.offsetParent
  end while el
  xy
end

#element_objectObject

accesses the object representing this Element in the DOM.



573
574
575
576
# File 'lib/vapir-common/element.rb', line 573

def element_object
  assert_exists
  @element_object
end

#exists?Boolean Also known as: exist?

Returns whether this element actually exists.

Returns:

  • (Boolean)


278
279
280
281
282
# File 'lib/vapir-common/element.rb', line 278

def exists?
  handling_existence_failure(:handle => proc { return false }, :assert_exists => false) do
    return !!locate
  end
end

#flash(options = {}) ⇒ Object

Flash the element the specified number of times. Defaults to 10 flashes.



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/vapir-common/element.rb', line 410

def flash(options={})
  if options.is_a?(Fixnum)
    options={:count => options}
    if config.warn_deprecated
      Kernel.warn_with_caller "DEPRECATION WARNING: #{self.class.name}\#flash takes an options hash - passing a number is deprecated. Please use #{self.class.name}\#flash(:count => #{options[:count]})"
    end
  end
  options={:count => 10, :sleep => 0.05}.merge(options)
  #options=handle_options(options, {:count => 10, :sleep => 0.05}, [:color])
  assert_exists do
    options[:count].times do
      with_highlight(options) do
        sleep options[:sleep]
      end
      sleep options[:sleep]
    end
  end
  nil
end

#htmlObject



111
112
113
# File 'lib/vapir-common/element.rb', line 111

def html
  outer_html
end

#inspectObject



619
620
621
622
623
# File 'lib/vapir-common/element.rb', line 619

def inspect
  "\#<#{self.class.name}:0x#{"%.8x"%(self.hash*2)}"+attributes_for_stringifying.map do |attr|
    " "+attr.first+'='+attr.last.inspect
  end.join('') + ">"
end

#locateObject

locates the element object for this element



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/vapir-common/element.rb', line 189

def locate
  if element_object_exists?
    return @element_object
  end
  new_element_object= begin
    case @how
    when :element_object
      assert_no_index
      if @element_object # if @element_object is already set, it must not exist, since we check #element_object_exists? above. 
        raise Vapir::Exception::UnableToRelocateException, "This #{self.class.name} has stopped existing. Tried to relocate it, but it was specified using #{how.inspect} and cannot be relocated."
      else
        @what
      end
    when :xpath
      assert_container_exists
      unless @container.respond_to?(:element_object_by_xpath)
        raise Vapir::Exception::MissingWayOfFindingObjectException, "Locating by xpath is not supported on the container #{@container.inspect}"
      end
      # todo/fix: implement index for this, using element_objects_by_xpath ? 
      assert_no_index
      by_xpath=@container.element_object_by_xpath(@what)
      match_candidates(by_xpath ? [by_xpath] : [], self.class.specifiers, self.class.all_dom_attr_aliases).first
    when :css
      assert_container_exists
      candidate_match_at_index(@index, method(:match_candidates), @container.containing_object.querySelectorAll(@what), self.class.specifiers, self.class.all_dom_attr_aliases)
    when :label
      assert_no_index
      unless document_object
        raise "No document object found for this #{self.inspect} - needed to search by id for label from #{@container.inspect}"
      end
      label_element = case @what
      when Label
        @what.locate!
        @what
      when String, Regexp
        page_container.label(:text, @what)
      else
        raise Vapir::Exception::MissingWayOfFindingObjectException, "This #{self.class} was specified as 'how'=:label; 'what' was expected to be a Label element or a String or Regexp to match label text. Given 'what'=#{@what.inspect} (#{@what.class})"
      end
      if label_element.exists?
        by_label=document_object.getElementById(label_element.for)
      end
      match_candidates(by_label ? [by_label] : [], self.class.specifiers, self.class.all_dom_attr_aliases).first
    when :attributes
      assert_container_exists
      specified_attributes=@what
      specifiers=self.class.specifiers.map{|spec| spec.merge(specified_attributes)}
      
      candidate_match_at_index(@index, method(:matched_candidates), specifiers, self.class.all_dom_attr_aliases)
    when nil
      assert_container_exists
      unless @what.nil?
        raise ArgumentError, "'what' was specified, but 'how' was not given (is nil)"
      end
      candidate_match_at_index(@index, method(:matched_candidates), self.class.specifiers, self.class.all_dom_attr_aliases)
    when :custom
      assert_container_exists
      # this allows a proc to be given as 'what', which is called yielding candidates, each being 
      # an instanted Element of this class. this might seem a bit odd - instantiating a bunch 
      # of elements in order to figure out which element_object to use in locating this one. 
      # the purpose is so that this Element can be relocated if we lose the element_object. 
      # the Elements that are yielded are instantiated by :element object which cannot be 
      # relocated. 
      #
      # this integrates with ElementCollection, where Enumerable methods #detect,
      # #select, and #reject are overridden to use it. 
      # 
      # the proc should return true (that is, not false or nil) when it likes the given Element - 
      # when it matches what it expects of this Element. 
      candidate_match_at_index(@index, method(:matched_candidates), self.class.specifiers, self.class.all_dom_attr_aliases) do |candidate|
        what.call(self.class.new(:element_object, candidate, @container.extra_for_contained))
      end
    else
      raise Vapir::Exception::MissingWayOfFindingObjectException, "Unknown 'how' given: #{@how.inspect} (#{@how.class}). 'what' was #{@what.inspect} (#{@what.class})"
    end
  end
  @element_object=new_element_object
end

#locate!Object



267
268
269
270
271
272
273
274
# File 'lib/vapir-common/element.rb', line 267

def locate!
  locate || begin
    klass=self.is_a?(Frame) ? Vapir::Exception::UnknownFrameException : Vapir::Exception::UnknownObjectException
    message="Unable to locate #{self.class}, using #{@how}"+(@what ? ": "+@what.inspect : '')+(@index ? ", index #{@index}" : "")
    message+="\non container: #{@container.inspect}" if @container
    raise(klass, message)
  end
end

#parent(options = {}) ⇒ Object

Return the element immediately containing this element. returns nil if there is no parent, or if the parent is the document.

this is cached; call parent(:reload => true) if you wish to uncache it.



434
435
436
437
438
439
440
441
442
443
444
# File 'lib/vapir-common/element.rb', line 434

def parent(options={})
  @parent=nil if options[:reload]
  @parent||=begin
    parentNode=element_object.parentNode
    if parentNode && parentNode != document_object # don't ascend up to the document. #TODO/Fix - for IE, comparing WIN32OLEs doesn't really work, this comparison is pointless. 
      base_element_class.factory(parentNode, extra_for_contained) # this is a little weird, passing extra_for_contained so that this is the container of its parent. 
    else
      nil
    end
  end
end

#pretty_print(pp) ⇒ Object



632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/vapir-common/element.rb', line 632

def pretty_print(pp)
  pp.object_address_group(self) do
    pp.seplist(attributes_for_stringifying, lambda { pp.text ',' }) do |attr|
      pp.breakable ' '
      pp.group(0) do
        pp.text attr.first
        pp.text ':'
        pp.breakable
        pp.pp attr.last
      end
    end
  end
end

#screen_centerObject

returns a two-element Vector containing the current position of the center of this element on the screen. intended to be used with mouse events’ screenX and screenY. developer.mozilla.org/en/DOM/event.screenX developer.mozilla.org/en/DOM/event.screenY

not yet implemented.



558
559
560
# File 'lib/vapir-common/element.rb', line 558

def screen_center
  screen_offset+dimensions.map{|dim| dim/2}
end

#screen_offsetObject

returns a two-element Vector containing the position of this element on the screen. see also #screen_center not yet implemented.

Raises:

  • (NotImplementedError)


547
548
549
# File 'lib/vapir-common/element.rb', line 547

def screen_offset
  raise NotImplementedError
end

#scroll_offsetObject

returns a two-element Vector containing the current scroll offset of this element relative to any scrolling parents. this is basically stolen from prototype - see www.prototypejs.org/api/element/cumulativescrolloffset



532
533
534
535
536
537
538
539
540
541
542
# File 'lib/vapir-common/element.rb', line 532

def scroll_offset
  xy=Vector[0,0]
  el=element_object
  begin
    if el.respond_to?(:scrollLeft) && el.respond_to?(:scrollTop) && (scroll_left=el.scrollLeft).is_a?(Numeric) && (scroll_top=el.scrollTop).is_a?(Numeric)
      xy+=Vector[scroll_left, scroll_top]
    end
    el=el.parentNode
  end while el
  xy
end

#text_nodesObject

returns an array of all text nodes below this element in the DOM heirarchy



482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# File 'lib/vapir-common/element.rb', line 482

def text_nodes
  # TODO: needs tests 
  assert_exists do
    recurse_text_nodes=proc do |rproc, e_obj|
      case e_obj.nodeType
      when 1 # TODO: name a constant ELEMENT_NODE, rather than magic number 
        object_collection_to_enumerable(e_obj.childNodes).inject([]) do |result, c_obj|
          result + rproc.call(rproc, c_obj)
        end
      when 3 # TODO: name a constant TEXT_NODE, rather than magic number 
        [e_obj.data]
      else
        #Kernel.warn("ignoring node of type #{e_obj.nodeType}")
        []
      end
    end
    recurse_text_nodes.call(recurse_text_nodes, element_object)
  end
end

#to_sObject



624
625
626
627
628
629
630
# File 'lib/vapir-common/element.rb', line 624

def to_s
  attrs=attributes_for_stringifying
  longest_label=attrs.inject(0) {|max, attr| [max, attr.first.size].max }
  "#{self.class.name}:0x#{"%.8x"%(self.hash*2)}\n"+attrs.map do |attr|
    (attr.first+": ").ljust(longest_label+2)+attr.last.inspect+"\n"
  end.join('')
end

#to_subtypeObject Also known as: to_factory

returns an Element that represents the same object as self, but is an instance of the most-specific class < self.class that can represent that object.

For example, if we have a table, get its first element, and call #to_factory on it:

a_table=browser.tables.first
=> #<Vapir::IE::Table:0x071bc70c index=:first tagName="TABLE">
a_element=a_table.elements.first
=> #<Vapir::IE::Element:0x071b856c index=:first tagName="TBODY" id="">
a_element.to_factory
=> #<Vapir::IE::TableBody:0x071af78c index=:first tagName="TBODY" id="">

we get back a Vapir::TableBody.



318
319
320
# File 'lib/vapir-common/element.rb', line 318

def to_subtype
  self.class.factory(element_object, @extra, @how, @what)
end

#visible?Boolean

Checks this element and its parents for display: none or visibility: hidden, these are the most common methods to hide an html element. Returns false if this seems to be hidden or a parent is hidden.

Returns:

  • (Boolean)


449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/vapir-common/element.rb', line 449

def visible? 
  assert_exists do
    element_to_check=element_object
    #nsIDOMDocument=jssh_socket.Components.interfaces.nsIDOMDocument
    really_visible=nil
    while element_to_check #&& !element_to_check.instanceof(nsIDOMDocument)
      if (style=element_object_style(element_to_check, document_object))
        # only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements), 
        # or 'visible'. ignore 'inherit'; keep looking upward. 
        # this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up. 
        # this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
        if really_visible==nil && (visibility=style.invoke('visibility'))
          visibility=visibility.strip.downcase
          if visibility=='hidden' || visibility=='collapse'
            really_visible=false
            return false # don't need to continue knowing it's not visible. 
          elsif visibility=='visible'
            really_visible=true # we don't return true yet because a parent with display of 'none' can override 
          end
        end
        # check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible' 
        display=style.invoke('display')
        if display && display.strip.downcase=='none'
          return false
        end
      end
      element_to_check=element_to_check.parentNode
    end
  end
  return true
end

#with_highlight(options = {}) ⇒ Object

takes a block. sets highlight on this element; calls the block; clears the highlight. the clear is in an ensure block so that you can call return from the given block.

takes an options hash; every argument is ignored except :highlight, which defaults to true; if set to false then the highlighting won’t actually happen, the block will just be called and its value returned.

also, you can nest these safely; it checks if you’re already highlighting before trying to set and subsequently clear the highlight.

the block is called within an #assert_exists block, so methods that highlight don’t need to also check existence as that’d be redundant.



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/vapir-common/element.rb', line 335

def with_highlight(options={})
  assert_exists do
    # yeah, this line is an unreadable mess, but I have to skip over it so many times debugging that it's worth just sticking it on one line 
    (options={:highlight => true}.merge(options)); (was_highlighting=@highlighting); (set_highlight(options) if !@highlighting && options[:highlight]); (@highlighting=true)
    begin; result=yield
    ensure
      @highlighting=was_highlighting
      if !@highlighting && options[:highlight]
        handling_existence_failure do
          clear_highlight(options)
        end
      end
    end
    result
  end
end