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.
- data/lib/vapir-common/browser.rb +1 -1
- data/lib/vapir-common/container.rb +42 -0
- data/lib/vapir-common/element.rb +100 -18
- data/lib/vapir-common/element_collection.rb +9 -1
- data/lib/vapir-common/elements/elements.rb +33 -25
- data/lib/vapir-common/exceptions.rb +7 -7
- data/lib/vapir-common/modal_dialog.rb +1 -1
- data/lib/vapir-common/page_container.rb +21 -0
- data/lib/vapir-common/specifier.rb +6 -1
- data/lib/vapir-common.rb +1 -1
- metadata +11 -7
data/lib/vapir-common/browser.rb
CHANGED
@@ -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::
|
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}
|
data/lib/vapir-common/element.rb
CHANGED
@@ -671,10 +671,8 @@ module Vapir
|
|
671
671
|
public
|
672
672
|
# Returns whether this element actually exists.
|
673
673
|
def exists?
|
674
|
-
|
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]
|
744
|
-
|
745
|
-
|
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
|
-
|
1069
|
-
|
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
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
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
|
-
|
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
|
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
|
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
|
-
|
470
|
-
|
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
|
479
|
-
fire_event
|
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 <
|
6
|
-
def initialize(message="")
|
7
|
-
super(message)
|
8
|
-
end
|
9
|
-
end
|
5
|
+
class VapirException < StandardError; end
|
10
6
|
|
11
|
-
class
|
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 <
|
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::
|
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.
|
269
|
+
match &&= candidate.nodeType==1
|
265
270
|
else
|
266
271
|
match=false
|
267
272
|
end
|
data/lib/vapir-common.rb
CHANGED
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:
|
4
|
+
prerelease: true
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 7
|
8
|
-
-
|
9
|
-
|
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
|
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
|
-
-
|
89
|
-
|
90
|
+
- 1
|
91
|
+
- 3
|
92
|
+
- 1
|
93
|
+
version: 1.3.1
|
90
94
|
requirements: []
|
91
95
|
|
92
96
|
rubyforge_project:
|