vapir-common 1.7.0 → 1.7.1.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|