vapir-common 1.7.2 → 1.8.0
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/History.txt +0 -5
- data/lib/vapir-common.rb +16 -4
- data/lib/vapir-common/browser.rb +189 -144
- data/lib/vapir-common/browsers.rb +21 -9
- data/lib/vapir-common/config.rb +341 -0
- data/lib/vapir-common/container.rb +160 -30
- data/lib/vapir-common/element.rb +65 -555
- data/lib/vapir-common/element_class_and_module.rb +378 -0
- data/lib/vapir-common/element_collection.rb +108 -20
- data/lib/vapir-common/elements/elements.rb +243 -67
- data/lib/vapir-common/external/core_extensions.rb +62 -0
- data/lib/vapir-common/handle_options.rb +1 -1
- data/lib/vapir-common/keycodes.rb +135 -0
- data/lib/vapir-common/options.rb +5 -38
- data/lib/vapir-common/page_container.rb +26 -21
- data/lib/vapir-common/specifier.rb +2 -2
- data/lib/vapir-common/version.rb +5 -0
- data/lib/vapir-common/waiter.rb +44 -90
- data/lib/vapir.rb +7 -0
- metadata +12 -27
- data/lib/vapir-common/testcase.rb +0 -89
- data/lib/vapir-common/win_window.rb +0 -1227
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'vapir-common/element'
|
2
2
|
require 'vapir-common/container'
|
3
|
-
|
3
|
+
require 'vapir-common/keycodes.rb'
|
4
4
|
module Vapir
|
5
5
|
module Frame
|
6
6
|
extend ElementHelper
|
@@ -54,6 +54,23 @@ module Vapir
|
|
54
54
|
!disabled
|
55
55
|
end
|
56
56
|
|
57
|
+
module WatirInputElementConfigCompatibility
|
58
|
+
def requires_typing
|
59
|
+
if config.warn_deprecated
|
60
|
+
Kernel.warn_with_caller "WARNING: #requires_typing is deprecated; please use the new config framework with config.type_keys="
|
61
|
+
end
|
62
|
+
config.type_keys = true
|
63
|
+
self
|
64
|
+
end
|
65
|
+
def abhors_typing
|
66
|
+
if config.warn_deprecated
|
67
|
+
Kernel.warn_with_caller "WARNING: #abhors_typing is deprecated; please use the new config framework with config.type_keys="
|
68
|
+
end
|
69
|
+
config.type_keys = false
|
70
|
+
self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
include WatirInputElementConfigCompatibility
|
57
74
|
end
|
58
75
|
module TextField
|
59
76
|
extend ElementHelper
|
@@ -65,6 +82,8 @@ module Vapir
|
|
65
82
|
container_single_method :text_field
|
66
83
|
container_collection_method :text_fields
|
67
84
|
|
85
|
+
default_how :name
|
86
|
+
|
68
87
|
dom_attr :size, :maxLength => :maxlength
|
69
88
|
alias_deprecated :getContents, :value
|
70
89
|
|
@@ -73,101 +92,138 @@ module Vapir
|
|
73
92
|
# to be consistent with similar methods #set and #append, returns the new value, though this will always be a blank string.
|
74
93
|
#
|
75
94
|
# takes options:
|
76
|
-
# :blur => true/false; whether or not to fire the onblur event when done.
|
77
|
-
# :highlight => true/false
|
95
|
+
# - :blur => true/false; whether or not to fire the onblur event when done.
|
96
|
+
# - :highlight => true/false
|
78
97
|
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
98
|
+
# Raises UnknownObjectException if the object can't be found
|
99
|
+
# Raises ObjectDisabledException if the object is disabled
|
100
|
+
# Raises ObjectReadOnlyException if the object is read only
|
82
101
|
def clear(options={})
|
83
102
|
options={:blur => true, :change => true, :select => true, :focus => true}.merge(options)
|
84
103
|
assert_enabled
|
85
104
|
assert_not_readonly
|
86
105
|
with_highlight(options) do
|
87
106
|
if options[:focus]
|
107
|
+
assert_exists(:force => true)
|
88
108
|
element_object.focus
|
89
109
|
fire_event('onFocus')
|
90
|
-
assert_exists(:force => true)
|
91
110
|
end
|
92
111
|
if options[:select]
|
112
|
+
assert_exists(:force => true)
|
93
113
|
element_object.select
|
94
114
|
fire_event("onSelect")
|
95
|
-
assert_exists(:force => true)
|
96
115
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
assert_exists(:force => true)
|
116
|
+
handling_existence_failure do
|
117
|
+
with_key_down(:keyCode => KeyCodes[:delete]) do
|
118
|
+
assert_exists(:force => true)
|
119
|
+
element_object.value = ''
|
120
|
+
end
|
121
|
+
end
|
104
122
|
if options[:change] && exists?
|
105
123
|
fire_event("onChange")
|
106
124
|
end
|
107
125
|
if options[:blur] && exists?
|
108
126
|
fire_event('onBlur')
|
109
127
|
end
|
110
|
-
self.value
|
128
|
+
exists? ? self.value : nil
|
111
129
|
end
|
112
130
|
end
|
131
|
+
private
|
132
|
+
# todo: move to Element or something? this applies to hitting a key anywhere, really - not just TextFields.
|
133
|
+
def with_key_down(fire_event_options={})
|
134
|
+
assert_exists(:force => true)
|
135
|
+
fire_event :onKeyDown, fire_event_options
|
136
|
+
yield if block_given?
|
137
|
+
assert_exists(:force => true)
|
138
|
+
fire_event :onKeyUp, fire_event_options
|
139
|
+
end
|
140
|
+
def type_key(key)
|
141
|
+
# the events created by the following are sort of wrong - for firefox, only one of keyCode
|
142
|
+
# or charCode should ever be set.
|
143
|
+
# for ie, keyCode is always set and charCode is ignored (there is no such property of IE events).
|
144
|
+
# I could overload this method on a browser-specific basis, but having both set in ff seems to
|
145
|
+
# do no harm, so I will not do that for the moment.
|
146
|
+
if PrintKeyCodes.key?(key)
|
147
|
+
with_key_down(:keyCode => PrintKeyCodes[key]) do
|
148
|
+
assert_exists(:force => true)
|
149
|
+
fire_event :onKeyPress, :keyCode => key.vapir_ord, :charCode => key.vapir_ord
|
150
|
+
yield if block_given?
|
151
|
+
end
|
152
|
+
elsif ShiftPrintKeyCodes.key?(key)
|
153
|
+
with_key_down(:keyCode => KeyCodes[:shift], :shiftKey => true) do
|
154
|
+
with_key_down(:keyCode => ShiftPrintKeyCodes[key], :shiftKey => true) do
|
155
|
+
assert_exists(:force => true)
|
156
|
+
fire_event :onKeyPress, :keyCode => key.vapir_ord, :charCode => key.vapir_ord, :shiftKey => true
|
157
|
+
yield if block_given?
|
158
|
+
end
|
159
|
+
end
|
160
|
+
else #?
|
161
|
+
with_key_down do
|
162
|
+
assert_exists(:force => true)
|
163
|
+
fire_event :onKeyPress, :keyCode => key.vapir_ord, :charCode => key.vapir_ord
|
164
|
+
yield if block_given?
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
public
|
113
169
|
# Appends the specified string value to the contents of the text box.
|
114
170
|
#
|
115
171
|
# returns the new value of the text field. this may not include all of what is given if there is a maxlength on the field.
|
116
172
|
#
|
117
173
|
# takes options:
|
118
|
-
# :blur => true/false; whether or not to file the onblur event when done.
|
119
|
-
# :highlight => true/false
|
174
|
+
# - :blur => true/false; whether or not to file the onblur event when done.
|
175
|
+
# - :highlight => true/false
|
120
176
|
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
177
|
+
# Raises UnknownObjectException if the object cant be found
|
178
|
+
# Raises ObjectDisabledException if the object is disabled
|
179
|
+
# Raises ObjectReadOnlyException if the object is read only
|
124
180
|
def append(value, options={})
|
125
181
|
options={:blur => true, :change => true, :select => true, :focus => true}.merge(options)
|
126
182
|
assert_enabled
|
127
183
|
assert_not_readonly
|
128
184
|
|
129
185
|
with_highlight(options) do
|
130
|
-
existing_value_chars=element_object.value.split(//)
|
131
|
-
new_value_chars=existing_value_chars+value.split(//)
|
132
|
-
#value_chars=value.split(//) # split on blank regexp (rather than iterating over each byte) for multibyte chars
|
186
|
+
existing_value_chars=element_object.value.split(//u)
|
187
|
+
new_value_chars=existing_value_chars+value.split(//u) # IE treats the string value is set to as utf8, and this is consistent with String#ord defined in core_ext
|
133
188
|
if self.type.downcase=='text' && maxlength && maxlength >= 0 && new_value_chars.length > maxlength
|
134
189
|
new_value_chars=new_value_chars[0...maxlength]
|
135
190
|
end
|
136
191
|
element_object.scrollIntoView
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
192
|
+
if options[:focus]
|
193
|
+
assert_exists(:force => true)
|
194
|
+
element_object.focus
|
195
|
+
fire_event('onFocus')
|
196
|
+
end
|
197
|
+
if options[:select]
|
198
|
+
assert_exists(:force => true)
|
199
|
+
element_object.select
|
200
|
+
fire_event("onSelect")
|
201
|
+
end
|
202
|
+
if config.type_keys
|
203
|
+
(existing_value_chars.length...new_value_chars.length).each do |i|
|
204
|
+
last_key = (i == new_value_chars.length - 1)
|
205
|
+
handling_existence_failure(:handle => (last_key ? :ignore : :raise)) do
|
206
|
+
type_key(new_value_chars[i]) do
|
207
|
+
assert_exists(:force => true)
|
208
|
+
element_object.value = new_value_chars[0..i].join('')
|
209
|
+
end
|
210
|
+
end
|
211
|
+
sleep config.typing_interval
|
149
212
|
end
|
150
|
-
|
151
|
-
|
152
|
-
element_object.value = new_value_chars[0..i].join('')
|
153
|
-
fire_event :onKeyDown # TODO/fix - keyCode for character typed
|
154
|
-
assert_exists(:force => true)
|
155
|
-
fire_event :onKeyPress
|
156
|
-
assert_exists(:force => true)
|
157
|
-
fire_event :onKeyUp
|
213
|
+
else
|
214
|
+
with_key_down do # simulate at least one keypress
|
158
215
|
assert_exists(:force => true)
|
216
|
+
element_object.value = new_value_chars.join('')
|
159
217
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
else
|
167
|
-
element_object.value = element_object.value + value
|
218
|
+
end
|
219
|
+
if options[:change] && exists?
|
220
|
+
handling_existence_failure { fire_event("onChange") }
|
221
|
+
end
|
222
|
+
if options[:blur] && exists?
|
223
|
+
handling_existence_failure { fire_event('onBlur') }
|
168
224
|
end
|
169
225
|
wait
|
170
|
-
self.value
|
226
|
+
exists? ? self.value : nil
|
171
227
|
end
|
172
228
|
end
|
173
229
|
# Sets the contents of the text field to the given value
|
@@ -175,18 +231,16 @@ module Vapir
|
|
175
231
|
# returns the new value of the text field. this may be shorter than what is given if there is a maxlength on the field.
|
176
232
|
#
|
177
233
|
# takes options:
|
178
|
-
# :blur => true/false; whether or not to file the onblur event when done.
|
179
|
-
# :highlight => true/false
|
234
|
+
# - :blur => true/false; whether or not to file the onblur event when done.
|
235
|
+
# - :highlight => true/false
|
180
236
|
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
237
|
+
# Raises UnknownObjectException if the object cant be found
|
238
|
+
# Raises ObjectDisabledException if the object is disabled
|
239
|
+
# Raises ObjectReadOnlyException if the object is read only
|
184
240
|
def set(value, options={})
|
185
241
|
with_highlight(options) do
|
186
242
|
clear(options.merge(:blur => false, :change => false))
|
187
|
-
assert_exists(:force => true)
|
188
243
|
append(value, options.merge(:focus => false, :select => false))
|
189
|
-
self.value
|
190
244
|
end
|
191
245
|
end
|
192
246
|
end
|
@@ -256,7 +310,7 @@ module Vapir
|
|
256
310
|
method_options={:highlight => true, :wait => true}.merge(method_options)
|
257
311
|
with_highlight(method_options) do
|
258
312
|
state_was=element_object.selected
|
259
|
-
element_object.selected=state
|
313
|
+
element_object.selected=state # TODO: if state is false and this isn't an option of a multiple select list, should this error?
|
260
314
|
if @extra[:select_list] && state_was != state
|
261
315
|
@extra[:select_list].fire_event(:onchange, method_options)
|
262
316
|
end
|
@@ -545,10 +599,95 @@ module Vapir
|
|
545
599
|
end
|
546
600
|
end
|
547
601
|
module HasRowsAndColumns
|
602
|
+
# returns a TableRow which is a row of this of this Table or TBody (not in a nested table).
|
603
|
+
# takes the usual arguments for specifying what you want - see http://github.com/vapir/vapir/wiki/Locators
|
604
|
+
def row(first=nil, second=nil)
|
605
|
+
element_by_howwhat(element_class_for(Vapir::TableRow), first, second, :extra => {:candidates => :rows})
|
606
|
+
end
|
607
|
+
|
608
|
+
# returns a TableCell which is a cell of this of this Table or TBody (not in a nested table).
|
609
|
+
# takes the usual arguments for specifying what you want - see http://github.com/vapir/vapir/wiki/Locators
|
610
|
+
def cell(first=nil, second=nil)
|
611
|
+
element_by_howwhat(element_class_for(Vapir::TableCell), first, second, :extra => {:candidates => proc do |container|
|
612
|
+
container_object=container.element_object
|
613
|
+
object_collection_to_enumerable(container_object.rows).inject([]) do |candidates, row|
|
614
|
+
candidates+object_collection_to_enumerable(row.cells).to_a
|
615
|
+
end
|
616
|
+
end})
|
617
|
+
end
|
618
|
+
|
548
619
|
# Returns a 2 dimensional array of text contents of each row and column of the table or tbody.
|
549
620
|
def to_a
|
550
621
|
rows.map{|row| row.cells.map{|cell| cell.text.strip}}
|
551
622
|
end
|
623
|
+
|
624
|
+
# Returns an array of hashes representing this table. This assumes that the table has one row
|
625
|
+
# with header information and any number of rows with data. Each element of the array is a hash
|
626
|
+
# whose keys are the header values (every hash has the same keys), and whose values correspond
|
627
|
+
# to the current row.
|
628
|
+
#
|
629
|
+
# +--------------+---------------+
|
630
|
+
# | First Header | Second Header |
|
631
|
+
# | First Data 1 | Second Data 1 |
|
632
|
+
# | First Data 2 | Second Data 2 |
|
633
|
+
# +--------------+---------------+
|
634
|
+
#
|
635
|
+
# Given the above table, #to_hashes will return
|
636
|
+
# [{'First Header' => 'First Data 1', 'Second Header' => 'Second Data 1'}, {'First Header' => 'First Data 2', 'Second Header' => 'Second Data 2'}]
|
637
|
+
#
|
638
|
+
# This method will correctly account for colSpans and return text from all cells underneath a
|
639
|
+
# given header on one row. However, this method makes no attempt to deal with any rowSpans and
|
640
|
+
# will probably not work with any table with rowSpans in either the header row or data rows.
|
641
|
+
#
|
642
|
+
# options:
|
643
|
+
# - :header_count (default 1) - the number of rows before the table data start.
|
644
|
+
# - :header_index (default whatever :header_count is) - the index of the row that contains
|
645
|
+
# header data which will be the keys of the hashes returned. (1-indexed)
|
646
|
+
# - :footer_count (default 0) - the number of rows to discard from the end, being footer
|
647
|
+
# information and not table data.
|
648
|
+
# - :separator (default ' ') - used to join cells when there is more than one cell
|
649
|
+
# underneath a header.
|
650
|
+
def to_hashes(options={})
|
651
|
+
options=handle_options(options, {:header_count => 1, :footer_count => 0, :separator => ' '}, [:header_index])
|
652
|
+
options[:header_index]||=options[:header_count]
|
653
|
+
|
654
|
+
col_headings=rows[options[:header_index]].cells.map do |cell|
|
655
|
+
{:colSpan => cell.colSpan || 1, :text => cell.text.strip}
|
656
|
+
end
|
657
|
+
|
658
|
+
body_range=(options[:header_count]+1 .. self.row_count-options[:footer_count])
|
659
|
+
return body_range.map do |row_index|
|
660
|
+
row=rows[row_index]
|
661
|
+
# cells_by_heading will contain an array of arrays of table cells
|
662
|
+
# underneath the col_heading corresponding to cells_by_heading's array index.
|
663
|
+
# if cells do not line up underneath the column heading, exception is raised.
|
664
|
+
cells_by_heading=[]
|
665
|
+
curr_heading_index=0
|
666
|
+
cols_in_curr_heading=0
|
667
|
+
row.cells.each do |cell|
|
668
|
+
curr_heading=col_headings[curr_heading_index]
|
669
|
+
cells_by_heading[curr_heading_index] ||= []
|
670
|
+
cells_by_heading[curr_heading_index] << cell
|
671
|
+
cols_in_curr_heading += cell.colSpan || 1
|
672
|
+
if cols_in_curr_heading == curr_heading[:colSpan]
|
673
|
+
curr_heading_index+=1
|
674
|
+
cols_in_curr_heading=0
|
675
|
+
elsif cols_in_curr_heading > curr_heading[:colSpan]
|
676
|
+
raise "Cells underneath heading #{curr_heading[:text].inspect} do not line up!"
|
677
|
+
end # else, we haven't got all the cells under the current heading; keep going
|
678
|
+
end
|
679
|
+
if curr_heading_index > col_headings.length
|
680
|
+
raise "Too many cells for the headings!"
|
681
|
+
elsif curr_heading_index < col_headings.length
|
682
|
+
raise "Too few cells for the headings!"
|
683
|
+
end
|
684
|
+
|
685
|
+
col_headings.zip(cells_by_heading).inject({}) do |row_hash, (heading, cells)|
|
686
|
+
cell_texts=cells.map(&:text).join(options[:separator]).strip
|
687
|
+
row_hash.merge(heading[:text] => cell_texts)
|
688
|
+
end
|
689
|
+
end
|
690
|
+
end
|
552
691
|
|
553
692
|
# iterates through the rows in the table. Yields a TableRow object
|
554
693
|
def each_row
|
@@ -603,6 +742,7 @@ module Vapir
|
|
603
742
|
nil
|
604
743
|
end
|
605
744
|
end
|
745
|
+
#--
|
606
746
|
# I was going to define #cell_count(index=nil) here as an alternative to #column_count
|
607
747
|
# but it seems confusing; to me #cell_count on a Table would count up all the cells in
|
608
748
|
# all rows, so going to avoid confusion and not do it.
|
@@ -617,6 +757,7 @@ module Vapir
|
|
617
757
|
|
618
758
|
# Returns an array containing the text of the cell in the specified index in each row.
|
619
759
|
def column_texts_at(column_index)
|
760
|
+
# TODO: since this is named as 'column', not 'cell', shouldn't it return cell_at_column?
|
620
761
|
rows.map do |row|
|
621
762
|
row.cells[column_index].text
|
622
763
|
end
|
@@ -641,7 +782,13 @@ module Vapir
|
|
641
782
|
# Returns an ElementCollection of cells in the row
|
642
783
|
element_collection :cells, :cells, TableCell
|
643
784
|
|
644
|
-
#
|
785
|
+
# returns a TableCell which is a cell of this of this row (not in a nested table).
|
786
|
+
# takes the usual arguments for specifying what you want - see http://github.com/vapir/vapir/wiki/Locators
|
787
|
+
def cell(first=nil, second=nil)
|
788
|
+
element_by_howwhat(element_class_for(Vapir::TableCell), first, second, :extra => {:candidates => :cells})
|
789
|
+
end
|
790
|
+
|
791
|
+
# Iterate over each cell in the row. same as #cells.each.
|
645
792
|
def each_cell
|
646
793
|
cells.each do |cell|
|
647
794
|
yield cell
|
@@ -654,24 +801,51 @@ module Vapir
|
|
654
801
|
cells[index]
|
655
802
|
end
|
656
803
|
|
804
|
+
# the number of columns in this row, accounting for cells with a colspan attribute greater than 1
|
657
805
|
def column_count
|
658
806
|
cells.inject(0) do |count, cell|
|
659
807
|
count+ (cell.colSpan || 1)
|
660
808
|
end
|
661
809
|
end
|
810
|
+
|
811
|
+
# the number of cells in this row
|
662
812
|
def cell_count
|
663
813
|
cells.length
|
664
814
|
end
|
665
815
|
|
666
|
-
# returns the
|
816
|
+
# returns the column index (starting at 0), taking into account colspans, of the table cell for which the given block returns true.
|
817
|
+
#
|
818
|
+
# if nothing matches the block, returns nil.
|
819
|
+
def column_count_where # :yields: table_cell
|
820
|
+
cells.inject(0) do |count, cell|
|
821
|
+
if yield cell
|
822
|
+
return count
|
823
|
+
end
|
824
|
+
count+(cell.colSpan || 1)
|
825
|
+
end
|
826
|
+
nil
|
827
|
+
end
|
828
|
+
|
829
|
+
# returns the cell of the current row at the given column index (starting from 0), taking
|
667
830
|
# into account conSpans of other cells.
|
668
831
|
#
|
669
832
|
# returns nil if index is greater than the number of columns of this row.
|
670
|
-
def
|
671
|
-
|
833
|
+
def cell_at_column(index)
|
834
|
+
#TODO: test
|
835
|
+
cells.each_by_index do |cell|
|
672
836
|
index=index-(cell.colSpan || 1)
|
673
|
-
index
|
837
|
+
return cell if index < 0
|
674
838
|
end
|
839
|
+
nil
|
840
|
+
end
|
841
|
+
|
842
|
+
# returns the cell of the current row at the given column index (starting from 0), taking
|
843
|
+
# into account conSpans of other cells.
|
844
|
+
#
|
845
|
+
# raises exception if the cell does not exist (that is, index is greater than the number of columns of this row).
|
846
|
+
def cell_at_column!(index)
|
847
|
+
#TODO: test
|
848
|
+
cell_at_column(index) || raise(Vapir::Exception::UnknownObjectException, "Unable to locate cell at column #{index}. Column count is #{column_count}\non container: #{@container.inspect}")
|
675
849
|
end
|
676
850
|
end
|
677
851
|
module TBody
|
@@ -687,7 +861,9 @@ module Vapir
|
|
687
861
|
end
|
688
862
|
module Table
|
689
863
|
def self.create_from_element(container, element)
|
690
|
-
|
864
|
+
if config.warn_deprecated
|
865
|
+
Kernel.warn_with_caller "DEPRECATION WARNING: create_from_element is deprecated. Please use (element).parent_table (element being the second argument to this function)"
|
866
|
+
end
|
691
867
|
element.parent_table
|
692
868
|
end
|
693
869
|
|