vapir-common 1.7.0 → 1.7.1.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -172,7 +172,7 @@ before you invoke Browser.new.
172
172
  exists?
173
173
  end
174
174
  def locate!(options={})
175
- locate(options) || raise(Vapir::Exception::NoMatchingWindowFoundException, "The browser window seems to be gone")
175
+ locate(options) || raise(Vapir::Exception::WindowGoneException, "The browser window seems to be gone")
176
176
  end
177
177
  def inspect
178
178
  "#<#{self.class}:0x#{(self.hash*2).to_s(16)} " + (exists? ? "url=#{url.inspect} title=#{title.inspect}" : "exists?=false") + '>'
@@ -112,6 +112,48 @@ module Vapir
112
112
  end
113
113
  result
114
114
  end
115
+ public
116
+ # catch exceptions that indicate some failure of something existing.
117
+ #
118
+ # takes an option, :handle, which indicates how the method should handle an
119
+ # encountered exception.
120
+ # :handle may be one of:
121
+ # * :ignore (default) - the exception is ignored and nil is returned.
122
+ # * :raise - the exception is raised (same as if this method weren't used at all).
123
+ # * :return - returns the exception which was raised.
124
+ # * Proc, Method - the proc or method is called with the exception as an argument.
125
+ #
126
+ # If no exception was raised, then the result of the give block is returned.
127
+ #--
128
+ # this may be overridden elsewhere to deal with any other stuff that indicates failure to exist, as it is
129
+ # to catch WIN32OLERuntimeErrors.
130
+ def handling_existence_failure(options={})
131
+ options=handle_options(options, :handle => :ignore)
132
+ begin
133
+ yield
134
+ rescue Vapir::Exception::ExistenceFailureException
135
+ handle_existence_failure($!, options)
136
+ end
137
+ end
138
+ private
139
+ # handles any errors encountered by #handling_existence_failure (either the
140
+ # common one or a browser-specific one)
141
+ def handle_existence_failure(error, options={})
142
+ options=handle_options(options, :handle => :ignore)
143
+ case options[:handle]
144
+ when :raise
145
+ raise error
146
+ when :ignore
147
+ nil
148
+ when :return
149
+ error
150
+ when Proc, Method
151
+ options[:handle].call(error)
152
+ else
153
+ raise ArgumentError, "Don't know what to do when told to handle by :handle => #{options[:handle].inspect}"
154
+ end
155
+ end
156
+ public
115
157
 
116
158
  def default_extra_for_contained
117
159
  extra={:container => self}
@@ -671,10 +671,8 @@ module Vapir
671
671
  public
672
672
  # Returns whether this element actually exists.
673
673
  def exists?
674
- begin
675
- !!locate
676
- rescue Vapir::Exception::UnknownObjectException, Exception::NoMatchingWindowFoundException # if the window itself is gone, certainly we don't exist.
677
- false
674
+ handling_existence_failure(:handle => proc { return false }) do
675
+ return !!locate
678
676
  end
679
677
  end
680
678
  alias :exist? :exists?
@@ -740,18 +738,10 @@ module Vapir
740
738
  result=yield
741
739
  ensure
742
740
  @highlighting=was_highlighting
743
- if !@highlighting && options[:highlight] && exists? # if we stopped existing during the highlight, don't try to clear.
744
- if Object.const_defined?('WIN32OLE') # if WIN32OLE exists, calling clear_highlight may raise WIN32OLERuntimeError, even though we just checked existence.
745
- exception_to_rescue=WIN32OLERuntimeError
746
- else # otherwise, make a dummy class, inheriting from Exception that won't ever be instantiated to be rescued.
747
- exception_to_rescue=(@@dummy_exception ||= Class.new(::Exception))
748
- end
749
- begin
741
+ if !@highlighting && options[:highlight]
742
+ handling_existence_failure do
743
+ assert_exists :force => true
750
744
  clear_highlight(highlight_options)
751
- rescue exception_to_rescue
752
- # apparently despite checking existence above, sometimes the element object actually disappears between checking its existence
753
- # and clear_highlight using it, raising WIN32OLERuntimeError.
754
- # don't actually do anything in the rescue block here.
755
745
  end
756
746
  end
757
747
  end
@@ -894,8 +884,94 @@ module Vapir
894
884
  def element_object_style(element_object, document_object)
895
885
  self.class.element_object_style(element_object, document_object)
896
886
  end
897
-
898
887
  public
888
+
889
+ # returns an array of all text nodes below this element in the DOM heirarchy
890
+ def text_nodes
891
+ # TODO: needs tests
892
+ assert_exists do
893
+ recurse_text_nodes=proc do |rproc, e_obj|
894
+ case e_obj.nodeType
895
+ when 1 # TODO: name a constant ELEMENT_NODE, rather than magic number
896
+ object_collection_to_enumerable(e_obj.childNodes).inject([]) do |result, c_obj|
897
+ result + rproc.call(rproc, c_obj)
898
+ end
899
+ when 3 # TODO: name a constant TEXT_NODE, rather than magic number
900
+ [e_obj.data]
901
+ else
902
+ #Kernel.warn("ignoring node of type #{e_obj.nodeType}")
903
+ []
904
+ end
905
+ end
906
+ recurse_text_nodes.call(recurse_text_nodes, element_object)
907
+ end
908
+ end
909
+ # returns an array of text nodes below this element in the DOM heirarchy which are visible -
910
+ # that is, their parent element is visible.
911
+ def visible_text_nodes
912
+ # TODO: needs tests
913
+ assert_exists do
914
+ # define a nice recursive function to iterate down through the children
915
+ recurse_text_nodes=proc do |rproc, e_obj, parent_visibility|
916
+ case e_obj.nodeType
917
+ when 1 # TODO: name a constant ELEMENT_NODE, rather than magic number
918
+ style=element_object_style(e_obj, document_object)
919
+ our_visibility = style && (visibility=style.invoke('visibility'))
920
+ unless our_visibility && ['hidden', 'collapse', 'visible'].include?(our_visibility=our_visibility.strip.downcase)
921
+ our_visibility = parent_visibility
922
+ end
923
+ if (display=style.invoke('display')) && display.strip.downcase=='none'
924
+ []
925
+ else
926
+ object_collection_to_enumerable(e_obj.childNodes).inject([]) do |result, c_obj|
927
+ result + rproc.call(rproc, c_obj, our_visibility)
928
+ end
929
+ end
930
+ when 3 # TODO: name a constant TEXT_NODE, rather than magic number
931
+ if ['hidden','collapse'].include?(parent_visibility)
932
+ []
933
+ else
934
+ [e_obj.data]
935
+ end
936
+ else
937
+ #Kernel.warn("ignoring node of type #{e_obj.nodeType}")
938
+ []
939
+ end
940
+ end
941
+
942
+ # determine the current visibility and display. TODO: this is copied/adapted from #visible?; should DRY
943
+ element_to_check=element_object
944
+ real_visibility=nil
945
+ while element_to_check #&& !element_to_check.instanceof(nsIDOMDocument)
946
+ if (style=element_object_style(element_to_check, document_object))
947
+ # only pay attention to the innermost definition that really defines visibility - one of 'hidden', 'collapse' (only for table elements),
948
+ # or 'visible'. ignore 'inherit'; keep looking upward.
949
+ # this makes it so that if we encounter an explicit 'visible', we don't pay attention to any 'hidden' further up.
950
+ # this style is inherited - may be pointless for firefox, but IE uses the 'inherited' value. not sure if/when ff does.
951
+ if real_visibility==nil && (visibility=style.invoke('visibility'))
952
+ visibility=visibility.strip.downcase
953
+ if ['hidden', 'collapse', 'visible'].include?(visibility)
954
+ real_visibility=visibility
955
+ end
956
+ end
957
+ # check for display property. this is not inherited, and a parent with display of 'none' overrides an immediate visibility='visible'
958
+ display=style.invoke('display')
959
+ if display && (display=display.strip.downcase)=='none'
960
+ # if display is none, then this element is not visible, and thus has no visible text nodes underneath.
961
+ return []
962
+ end
963
+ end
964
+ element_to_check=element_to_check.parentNode
965
+ end
966
+ recurse_text_nodes.call(recurse_text_nodes, element_object, real_visibility)
967
+ end
968
+ end
969
+ # returns an visible text inside this element by concatenating text nodes below this element in the DOM heirarchy which are visible.
970
+ def visible_text
971
+ # TODO: needs tests
972
+ visible_text_nodes.join('')
973
+ end
974
+
899
975
  # returns a Vector with two elements, the x,y
900
976
  # coordinates of this element (its top left point)
901
977
  # from the top left edge of the window
@@ -1065,8 +1141,14 @@ module Vapir
1065
1141
  object.to_array
1066
1142
  elsif Object.const_defined?('WIN32OLE') && object.is_a?(WIN32OLE)
1067
1143
  array=[]
1068
- (0...object.length).each do |i|
1069
- array << object.item(i)
1144
+ length = object.length
1145
+ (0...length).each do |i|
1146
+ begin
1147
+ array << object.item(i)
1148
+ rescue WIN32OLERuntimeError
1149
+ # not rescuing, just adding information
1150
+ raise $!.class, "accessing item #{i} of #{length}, encountered:\n"+$!.message, $!.backtrace
1151
+ end
1070
1152
  end
1071
1153
  array
1072
1154
  else
@@ -32,13 +32,21 @@ module Vapir
32
32
  def each_with_element_index
33
33
  index=1
34
34
  candidates.each do |candidate|
35
- yield @collection_class.new(:index, nil, @extra.merge(:index => index, :element_object => candidate)), index
35
+ yield @collection_class.new(:index, nil, @extra.merge(:index => index, :element_object => candidate, :locate => false)), index
36
36
  index+=1
37
37
  end
38
38
  self
39
39
  end
40
40
  alias each_with_index each_with_element_index
41
41
 
42
+ # yields each element, specified by index (as opposed to by :element_object as #each yields)
43
+ # same as #each_with_index, except it doesn't yield the index number.
44
+ def each_by_index # :yields: element
45
+ each_with_element_index do |element, i|
46
+ yield element
47
+ end
48
+ end
49
+ # returns the element at the given index in the collection. indices start at 1.
42
50
  def [](index)
43
51
  at(index)
44
52
  end
@@ -386,22 +386,24 @@ module Vapir
386
386
  assert_enabled
387
387
  any_matched=false
388
388
  with_highlight(method_options) do
389
- # using #each_with_index (rather than #each) because sometimes the OLE object goes away when a
390
- # new option is selected (seems to be related to javascript events) and it has to be relocated.
391
- # see documentation on ElementCollection#each_with_index vs. #each.
392
- self.options.each_with_index do |option,i|
393
- # javascript events on previous option selections can cause the select list or its options to change, so this may not actually exist. but only check if we've actually done anything.
394
- break if any_matched && !option.exists?
395
- if yield option
396
- any_matched=true
397
- option.set_selected(true, method_options) # note that this fires the onchange event on this SelectList
398
- if !self.exists? || !multiple? # javascript events firing can cause us to stop existing at this point. we should not continue if we don't exist.
399
- break
389
+ handling_existence_failure do
390
+ # using #each_by_index (rather than #each) because sometimes the OLE object goes away when a
391
+ # new option is selected (seems to be related to javascript events) and it has to be relocated.
392
+ # see documentation on ElementCollection#each_by_index vs. #each.
393
+ self.options.each_by_index do |option|
394
+ # javascript events on previous option selections can cause the select list or its options to change, so this may not actually exist. but only check if we've actually done anything.
395
+ break if any_matched && !option.exists?
396
+ if yield option
397
+ any_matched=true
398
+ option.set_selected(true, method_options) # note that this fires the onchange event on this SelectList
399
+ if !self.exists? || !multiple? # javascript events firing can cause us to stop existing at this point. we should not continue if we don't exist.
400
+ break
401
+ end
400
402
  end
401
403
  end
402
404
  end
403
405
  if !any_matched
404
- raise Vapir::Exception::NoValueFoundException, "Could not find any options matching those specified on #{self.inspect}"
406
+ raise Vapir::Exception::NoValueFoundException, "Could not find any options matching those specified on #{self.inspect}.\nAvailable options are: \n#{options.map{|option| option.inspect}.join("\n")}"
405
407
  end
406
408
  self
407
409
  end
@@ -416,8 +418,8 @@ module Vapir
416
418
 
417
419
  # Unchecks the radio button or check box element.
418
420
  # Raises ObjectDisabledException exception if element is disabled.
419
- def clear
420
- set(false)
421
+ def clear(options={})
422
+ set(false, options)
421
423
  end
422
424
 
423
425
  end
@@ -438,17 +440,18 @@ module Vapir
438
440
  #
439
441
  # Fires the onchange event if value changes.
440
442
  # Fires the onclick event the state is true.
441
- def set(state=true)
442
- with_highlight do
443
+ def set(state=true, options={})
444
+ options=handle_options(options, :highlight => true, :wait => true)
445
+ with_highlight(options) do
443
446
  assert_enabled
444
447
  if checked!=state
445
448
  element_object.checked=state
446
- fire_event :onchange if exists? # don't fire event if we stopped existing due to change in state
449
+ fire_event(:onchange, options) if exists? # don't fire event if we stopped existing due to change in state
447
450
  end
448
451
  if state && exists?
449
- fire_event :onclick # fire this even if the state doesn't change; javascript can respond to clicking an already-checked radio.
452
+ fire_event(:onclick, options) # fire this even if the state doesn't change; javascript can respond to clicking an already-checked radio.
450
453
  end
451
- wait
454
+ wait if options[:wait]
452
455
  end
453
456
  return self
454
457
  end
@@ -466,8 +469,13 @@ module Vapir
466
469
  inspect_these :checked
467
470
  # Checks this check box, or clears (defaults to setting if no argument is given)
468
471
  # Raises ObjectDisabledException exception if element is disabled.
469
- def set(state=true)
470
- with_highlight do
472
+ #
473
+ # takes options:
474
+ # * :highlight => true/false (defaults to true)
475
+ # * :wait => true/false (defaults to false)
476
+ def set(state=true, options={})
477
+ options=handle_options(options, :highlight => true, :wait => true)
478
+ with_highlight(options) do
471
479
  assert_enabled
472
480
  if checked!=state
473
481
  if browser_class.name != 'Vapir::Firefox' # compare by name to not trigger autoload or raise NameError if not loaded
@@ -475,10 +483,10 @@ module Vapir
475
483
  # todo/fix: this is browser-specific stuff, shouldn't it be in the browser-specific class?
476
484
  element_object.checked=state
477
485
  end
478
- fire_event :onclick if exists? # sometimes previous actions can cause self to stop existing
479
- fire_event :onchange if exists?
486
+ fire_event(:onclick, options) if exists? # sometimes previous actions can cause self to stop existing
487
+ fire_event(:onchange, options) if exists?
480
488
  end
481
- wait
489
+ wait if options[:wait]
482
490
  end
483
491
  return self
484
492
  end
@@ -648,7 +656,7 @@ module Vapir
648
656
 
649
657
  def column_count
650
658
  cells.inject(0) do |count, cell|
651
- count+ cell.colSpan || 1
659
+ count+ (cell.colSpan || 1)
652
660
  end
653
661
  end
654
662
  def cell_count
@@ -2,16 +2,15 @@ module Vapir
2
2
  module Exception
3
3
 
4
4
  # Root class for all Vapir Exceptions
5
- class VapirException < RuntimeError
6
- def initialize(message="")
7
- super(message)
8
- end
9
- end
5
+ class VapirException < StandardError; end
10
6
 
11
- class NoBrowserException < VapirException; end
7
+ # Base class for variouss sorts of errors when a thing does not exist
8
+ class ExistenceFailureException < VapirException; end
12
9
 
10
+ class NoBrowserException < ExistenceFailureException; end
11
+
13
12
  # This exception is thrown if an attempt is made to access an object that doesn't exist
14
- class UnknownObjectException < VapirException; end
13
+ class UnknownObjectException < ExistenceFailureException; end
15
14
 
16
15
  # This exception is raised if attempting to relocate an Element that was located in a way that does not support relocating
17
16
  class UnableToRelocateException < UnknownObjectException; end
@@ -33,6 +32,7 @@ module Vapir
33
32
 
34
33
  class WindowException < VapirException; end
35
34
  # This exception is thrown if the window cannot be found
35
+ class WindowGoneException < ExistenceFailureException; end
36
36
  class NoMatchingWindowFoundException < WindowException; end
37
37
  class WindowFailedToCloseException < WindowException; end
38
38
 
@@ -11,7 +11,7 @@ module Vapir
11
11
  alias initialize default_initialize
12
12
 
13
13
  def locate!(options={})
14
- exists? || raise(Vapir::Exception::NoMatchingWindowFoundException, "The modal dialog seems to have stopped existing.")
14
+ exists? || raise(Vapir::Exception::WindowGoneException, "The modal dialog seems to have stopped existing.")
15
15
  end
16
16
  alias assert_exists locate!
17
17
 
@@ -0,0 +1,21 @@
1
+ module Vapir
2
+ module PageContainer
3
+ include Vapir::Container
4
+ def containing_object
5
+ document_object
6
+ end
7
+ def document_element
8
+ document_object.documentElement || raise(Exception::ExistenceFailureException, "document_object.documentElement was nil")
9
+ end
10
+ def title
11
+ document_object.title
12
+ end
13
+ # The url of the page object.
14
+ def url
15
+ document_object.location.href
16
+ end
17
+ def page_container
18
+ self
19
+ end
20
+ end
21
+ end
@@ -226,6 +226,11 @@ module Vapir
226
226
  # IF YOU CHANGE THIS CODE CHANGE THE CORRESPONDING JAVASCRIPT ABOVE TOO
227
227
  matched_candidates=[]
228
228
  candidates.each do |candidate|
229
+ # this bit isn't reflected in the javascript above because firefox doesn't behave this way, returning nil
230
+ if candidate==nil
231
+ raise Exception::ExistenceFailureException, "when searching for an element, a candidate was nil. (this tends to happen when a page is changing and things stop existing.)\nspecifiers are: #{specifiers_list.inspect}"
232
+ end
233
+
229
234
  candidate_attributes=proc do |attr|
230
235
  attrs=[]
231
236
  if Object.const_defined?('WIN32OLE') && candidate.is_a?(WIN32OLE)
@@ -261,7 +266,7 @@ module Vapir
261
266
  end
262
267
  else
263
268
  if candidate.object_respond_to?(:nodeType)
264
- match &&= candidate.candidate.nodeType==1
269
+ match &&= candidate.nodeType==1
265
270
  else
266
271
  match=false
267
272
  end
data/lib/vapir-common.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Vapir
2
2
  module Common
3
- VERSION = '1.7.0'
3
+ VERSION = '1.7.1.rc1'
4
4
  end
5
5
  end
6
6
  require 'vapir-common/browser'
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vapir-common
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ prerelease: true
5
5
  segments:
6
6
  - 1
7
7
  - 7
8
- - 0
9
- version: 1.7.0
8
+ - 1
9
+ - rc1
10
+ version: 1.7.1.rc1
10
11
  platform: ruby
11
12
  authors:
12
13
  - Ethan
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-06-02 00:00:00 -04:00
18
+ date: 2010-08-06 00:00:00 -04:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -47,6 +48,7 @@ files:
47
48
  - lib/vapir-common/browser.rb
48
49
  - lib/vapir-common/browsers.rb
49
50
  - lib/vapir-common/container.rb
51
+ - lib/vapir-common/page_container.rb
50
52
  - lib/vapir-common/modal_dialog.rb
51
53
  - lib/vapir-common/specifier.rb
52
54
  - lib/vapir-common/element.rb
@@ -82,11 +84,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
84
  version: "0"
83
85
  required_rubygems_version: !ruby/object:Gem::Requirement
84
86
  requirements:
85
- - - ">="
87
+ - - ">"
86
88
  - !ruby/object:Gem::Version
87
89
  segments:
88
- - 0
89
- version: "0"
90
+ - 1
91
+ - 3
92
+ - 1
93
+ version: 1.3.1
90
94
  requirements: []
91
95
 
92
96
  rubyforge_project: