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
-
#browser ⇒ Object
readonly
the Vapir::Browser this element is on.
-
#how ⇒ Object
readonly
Returns the value of attribute how.
-
#index ⇒ Object
readonly
Returns the value of attribute index.
-
#page_container ⇒ Object
readonly
the Vapir::PageContainer containing this element (a Browser, Frame, or ModalDialogDocument).
-
#what ⇒ Object
readonly
Returns the value of attribute what.
Class Method Summary collapse
Instance Method Summary collapse
-
#attr(attribute) ⇒ Object
method to access dom attributes by defined aliases.
-
#attributes_for_stringifying ⇒ Object
used by inspect, to_s, and pretty_print to determine what to show.
-
#browser_window_object ⇒ Object
returns the underlying object representing the browser.
-
#client_center ⇒ Object
returns a two-element Vector with the position of the center of this element on the client area.
-
#client_offset ⇒ Object
returns a two-element Vector containing the offset of this element on the client area.
- #configuration_parent ⇒ Object
- #container ⇒ Object
-
#content_window_object ⇒ Object
returns the content window object of the current page on the browser (this is the ‘window’ object in javascript).
-
#default_initialize(how, what, extra = {}) ⇒ Object
(also: #initialize)
the class-specific Elements may implement their own #initialize, but should call to this after they’ve done their stuff.
-
#dimensions ⇒ Object
returns a two-element Vector with the width and height of this element.
-
#document_center ⇒ Object
returns a two-element Vector with the position of the center of this element on the document.
- #document_object ⇒ Object
-
#document_offset ⇒ Object
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.
-
#element_object ⇒ Object
accesses the object representing this Element in the DOM.
-
#exists? ⇒ Boolean
(also: #exist?)
Returns whether this element actually exists.
-
#flash(options = {}) ⇒ Object
Flash the element the specified number of times.
- #html ⇒ Object
- #inspect ⇒ Object
-
#locate ⇒ Object
locates the element object for this element.
- #locate! ⇒ Object
-
#parent(options = {}) ⇒ Object
Return the element immediately containing this element.
- #pretty_print(pp) ⇒ Object
-
#screen_center ⇒ Object
returns a two-element Vector containing the current position of the center of this element on the screen.
-
#screen_offset ⇒ Object
returns a two-element Vector containing the position of this element on the screen.
-
#scroll_offset ⇒ Object
returns a two-element Vector containing the current scroll offset of this element relative to any scrolling parents.
-
#text_nodes ⇒ Object
returns an array of all text nodes below this element in the DOM heirarchy.
-
#to_s ⇒ Object
returns a string representation of this element with each attribute on its own line.
-
#to_subtype ⇒ Object
(also: #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.
-
#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.
-
#with_highlight(options = {}) ⇒ Object
takes a block.
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, #without_waiting
Instance Attribute Details
#browser ⇒ Object (readonly)
the Vapir::Browser this element is on
586 587 588 |
# File 'lib/vapir-common/element.rb', line 586 def browser @browser end |
#how ⇒ Object (readonly)
Returns the value of attribute how.
107 108 109 |
# File 'lib/vapir-common/element.rb', line 107 def how @how end |
#index ⇒ Object (readonly)
Returns the value of attribute index.
109 110 111 |
# File 'lib/vapir-common/element.rb', line 109 def index @index end |
#page_container ⇒ Object (readonly)
the Vapir::PageContainer containing this element (a Browser, Frame, or ModalDialogDocument)
588 589 590 |
# File 'lib/vapir-common/element.rb', line 588 def page_container @page_container end |
#what ⇒ Object (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
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 |
# File 'lib/vapir-common/element.rb', line 659 def object_collection_to_enumerable(object) if object.is_a?(Enumerable) object elsif Object.const_defined?('JavascriptObject') && object.is_a?(JavascriptObject) 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"+$!., $!.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.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/vapir-common/element.rb', line 291 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_stringifying ⇒ Object
used by inspect, to_s, and pretty_print to determine what to show
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 |
# File 'lib/vapir-common/element.rb', line 607 def 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_object ⇒ Object
returns the underlying object representing the browser.
601 602 603 604 |
# File 'lib/vapir-common/element.rb', line 601 def browser_window_object assert_container @container.browser_window_object end |
#client_center ⇒ Object
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
528 529 530 |
# File 'lib/vapir-common/element.rb', line 528 def client_center client_offset+dimensions.map{|dim| dim/2} end |
#client_offset ⇒ Object
returns a two-element Vector containing the offset of this element on the client area. see also #client_center
519 520 521 |
# File 'lib/vapir-common/element.rb', line 519 def client_offset document_offset-scroll_offset end |
#configuration_parent ⇒ Object
13 14 15 |
# File 'lib/vapir-common/element.rb', line 13 def configuration_parent @container ? @container.config : @browser ? @browser.config : browser_class.config end |
#container ⇒ Object
580 581 582 583 |
# File 'lib/vapir-common/element.rb', line 580 def container assert_container @container end |
#content_window_object ⇒ Object
returns the content window object of the current page on the browser (this is the ‘window’ object in javascript).
596 597 598 599 |
# File 'lib/vapir-common/element.rb', line 596 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
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 |
#dimensions ⇒ Object
returns a two-element Vector with the width and height of this element.
566 567 568 |
# File 'lib/vapir-common/element.rb', line 566 def dimensions Vector[element_object.offsetWidth, element_object.offsetHeight] end |
#document_center ⇒ Object
returns a two-element Vector with the position of the center of this element on the document.
571 572 573 |
# File 'lib/vapir-common/element.rb', line 571 def document_center document_offset+dimensions.map{|dim| dim/2} end |
#document_object ⇒ Object
590 591 592 593 |
# File 'lib/vapir-common/element.rb', line 590 def document_object assert_container @container.document_object end |
#document_offset ⇒ Object
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
507 508 509 510 511 512 513 514 515 |
# File 'lib/vapir-common/element.rb', line 507 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_object ⇒ Object
accesses the object representing this Element in the DOM.
576 577 578 579 |
# File 'lib/vapir-common/element.rb', line 576 def element_object assert_exists @element_object end |
#exists? ⇒ Boolean Also known as: exist?
Returns whether this element actually exists.
281 282 283 284 285 |
# File 'lib/vapir-common/element.rb', line 281 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.
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/vapir-common/element.rb', line 413 def flash(={}) if .is_a?(Fixnum) ={:count => } 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 => #{[:count]})" end end ={:count => 10, :sleep => 0.05}.merge() #options=handle_options(options, {:count => 10, :sleep => 0.05}, [:color]) assert_exists do [:count].times do with_highlight() do sleep [:sleep] end sleep [:sleep] end end nil end |
#html ⇒ Object
111 112 113 |
# File 'lib/vapir-common/element.rb', line 111 def html outer_html end |
#inspect ⇒ Object
628 629 630 631 632 |
# File 'lib/vapir-common/element.rb', line 628 def inspect "\#<#{self.class.name}:0x#{"%.8x"%(self.hash*2)}"+.map do |attr| " "+attr.first+'='+attr.last.inspect end.join('') + ">" end |
#locate ⇒ Object
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 275 276 277 |
# File 'lib/vapir-common/element.rb', line 267 def locate! locate || begin klass=self.is_a?(Frame) ? Vapir::Exception::UnknownFrameException : Vapir::Exception::UnknownObjectException using = [] using << "#{@how}: #{@what.inspect}" if @how using << "index: #{@index}" if @index ="Unable to locate #{self.class}" + (using.any? ? ", using #{using.join(", ")}" : "") +="\non container: #{@container.inspect}" if @container raise(klass, ) 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.
437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/vapir-common/element.rb', line 437 def parent(={}) @parent=nil if [: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
644 645 646 647 648 649 650 651 652 653 654 655 656 |
# File 'lib/vapir-common/element.rb', line 644 def pretty_print(pp) pp.object_address_group(self) do pp.seplist(, 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_center ⇒ Object
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.
561 562 563 |
# File 'lib/vapir-common/element.rb', line 561 def screen_center screen_offset+dimensions.map{|dim| dim/2} end |
#screen_offset ⇒ Object
returns a two-element Vector containing the position of this element on the screen. see also #screen_center not yet implemented.
550 551 552 |
# File 'lib/vapir-common/element.rb', line 550 def screen_offset raise NotImplementedError end |
#scroll_offset ⇒ Object
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
535 536 537 538 539 540 541 542 543 544 545 |
# File 'lib/vapir-common/element.rb', line 535 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_nodes ⇒ Object
returns an array of all text nodes below this element in the DOM heirarchy
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 |
# File 'lib/vapir-common/element.rb', line 485 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_s ⇒ Object
returns a string representation of this element with each attribute on its own line. this returns the same information as #inspect, but formatted somewhat more readably. you might also be interested in pretty-printing the element; see the pp library.
636 637 638 639 640 641 642 |
# File 'lib/vapir-common/element.rb', line 636 def to_s attrs= 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_subtype ⇒ Object 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.
321 322 323 |
# File 'lib/vapir-common/element.rb', line 321 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.
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 480 481 482 |
# File 'lib/vapir-common/element.rb', line 452 def visible? assert_exists do element_to_check=element_object #nsIDOMDocument=firefox_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.
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/vapir-common/element.rb', line 338 def with_highlight(={}) 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 (={:highlight => true}.merge()); (was_highlighting=@highlighting); (set_highlight() if !@highlighting && [:highlight]); (@highlighting=true) begin; result=yield ensure @highlighting=was_highlighting if !@highlighting && [:highlight] handling_existence_failure do clear_highlight() end end end result end end |