marekj-watirloo 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,137 @@
1
+ =begin rdoc
2
+ Look Ma!, I can Has Reflect The Browser
3
+
4
+ Watir::Reflector module added to watir.
5
+ Run the script to reflect watir elements. reflections create wrapper methods
6
+ with suggested semantic naming based on id, name, value or combination.
7
+ the intention is to create a scaffolding for Watirloo::Page elements.
8
+ author: marekj
9
+ works with IE class and DOM elements.
10
+ =end
11
+ module Watir
12
+
13
+ # Watirloo::Page objects scaffold creation. Talks to the current page and reflects
14
+ # the watir elements to be used for semantic test objects tests.
15
+ module Reflector
16
+
17
+ @@reflectable_list = [
18
+ :text_fields,
19
+ :radios,
20
+ :checkboxes,
21
+ :select_lists
22
+ ]
23
+
24
+ #cleanup the def name for some kind of semantic name
25
+ def suggest_def_name(how)
26
+ how.gsub!(/_+/,'_') # double underscores to one
27
+ how.gsub!(/^_/, '') # if it begins with undrscore kill it.
28
+ how.gsub!(/\s+/, '_') # kill spaces if for some strange reason exist
29
+ how = how[0,1].downcase << how[1,how.size] #downcase firs char
30
+ end
31
+
32
+ # glean(:text_fields, [:id, :name, :value]
33
+ # glean(:radios, [:id, :name, :value])
34
+ # glean and make a map of types and attributes needed for reflection
35
+ # this should be private I think
36
+ def glean(types, attribs)
37
+ result = []
38
+ send(types).each do |el|
39
+ subresult = {}
40
+ attribs.each do |key|
41
+ v = el.attribute_value key.to_s
42
+ subresult.update key => v
43
+ end
44
+ result << subresult
45
+ end
46
+ return result
47
+ end
48
+
49
+ # example make_reflection(:checkboxes) # => [ defs, setters, faces]
50
+ # returns array of def wrappers, setters for elements and face definitions configs.
51
+ def make_reflection(types)
52
+ attribs = [:id, :name, :value]
53
+ faces = glean(types, attribs)
54
+ watir_method = types.id2name.chop
55
+ if watir_method == 'checkboxe'
56
+ watir_method = 'checkbox' #ooopps ... irregular plural
57
+ end
58
+ def_results = "# #{types.id2name.upcase}: def wrappers with suggested semantic names for elements\n" #holds definition wrappers
59
+ set_results = "# #{types.id2name.upcase}: setters calling def wrappers with captured values\n" #holds setters with gleaned values
60
+ face_results = "# #{types.id2name.upcase}: face definitions\n" #holds faces
61
+
62
+ faces.each do |face|
63
+ id, name, value = face[:id], face[:name], face[:value]
64
+
65
+ if id != ''
66
+ how, how_s = id, :id
67
+ elsif name != ''
68
+ how, how_s = name, :name
69
+ elsif value != ''
70
+ how, how_s = value, :value
71
+ end
72
+
73
+ def_name = suggest_def_name(how)
74
+
75
+ case types
76
+ when :checkboxes, :radios
77
+ extra_value = ", '#{value}'" #for checkboxes and radios
78
+ def_value = "_#{value}" #for checkboxes and radios
79
+ def_results << "\ndef #{def_name}#{def_value}\n\s\s@b.#{watir_method}(:#{how_s}, '#{how}'#{extra_value})\nend\n"
80
+ set_results << "#{def_name}#{def_value}.set\n"
81
+ face_results << ":#{def_name}#{def_value} => [:#{watir_method}, :#{how_s}, '#{how}'#{extra_value}]\n"
82
+
83
+ when :select_lists
84
+ # round trip back to browser for items and contents
85
+ value = eval("select_list(:#{how_s}, '#{how}').getSelectedItems")
86
+ items = eval("select_list(:#{how_s}, '#{how}').getAllContents")
87
+
88
+ def_results << "def #{def_name}\n\s\s@b.select_list(:#{how_s}, '#{how}')\nend\n"
89
+ set_results << "@@#{def_name}_items=#{items.inspect}\n" #class vars for values collections
90
+ set_results << "#{def_name}.set #{value.inspect}\n"
91
+ face_results << "#:#{def_name} => [:select_list, :#{how_s}, '#{name}}']\n"
92
+
93
+ else
94
+ def_results << "\ndef #{def_name}#{def_value}\n\s\s@b.#{watir_method}(:#{how_s}, '#{how}'#{extra_value})\nend\n"
95
+ set_results << "#{def_name}#{def_value}.set\n"
96
+ face_results << "\n#:#{def_name}#{def_value} = [:#{watir_method}, :#{how_s}, '#{how}#{extra_value}']\n"
97
+
98
+ end
99
+
100
+ end
101
+
102
+ return [def_results, set_results, face_results]
103
+ end
104
+ private :suggest_def_name, :glean, :make_reflection
105
+
106
+ # public interface for Reflector.
107
+ # ie.reflect(:all) # => returns object definitions for entire dom using ie as container
108
+ # ie.frame('main').reflect(:select_lists) # => returns definitions for select_lists only contained by the frame
109
+ # ie.div(:id, 'main').div(:id, 'content').reflect(:all) # => definitions for all supported elements contained by a div 'content' inside div 'main'
110
+ # you can be as granular as needed
111
+ def reflect(watir_types=:all)
112
+ results = []
113
+ case watir_types
114
+ when :all
115
+ @@reflectable_list.each do |types|
116
+ results << make_reflection(types)
117
+ end
118
+ else
119
+ unless @@reflectable_list.include?(watir_types)
120
+ raise ArgumentError, "reflect method does not respond to this argument: #{watir_method}"
121
+ end
122
+ results << make_reflection(watir_types)
123
+ end
124
+ return results
125
+ end
126
+ end
127
+
128
+ # ducktape IE container and include the Reflector.
129
+ class IE
130
+ include Reflector
131
+ end
132
+
133
+ module Container
134
+ include Reflector
135
+ end
136
+
137
+ end
@@ -0,0 +1,376 @@
1
+ gem 'watir', '>=1.6.2'
2
+ require 'watir'
3
+ require 'watir/ie'
4
+
5
+ module Watir
6
+
7
+ # for firefox and ie
8
+ module RadioCheckGroupCommonWatir
9
+
10
+ # size or count of controls in a group
11
+ def size
12
+ @o.size
13
+ end
14
+ alias count size
15
+
16
+ # sets control in a group by either position in a group
17
+ # or by hidden value attribute
18
+ def set(what)
19
+ if what.kind_of?(Array)
20
+ what.each {|thing| set thing } #calls itself with Fixnum or String
21
+ else
22
+ if what.kind_of?(Fixnum)
23
+ get_by_position(what).set
24
+ elsif what.kind_of?(String)
25
+ get_by_value(what).set
26
+ else
27
+ raise ::Watir::Exception::WatirException, "argument error #{what} not allowed"
28
+ end
29
+ end
30
+ end
31
+
32
+ # returns array of value attributes
33
+ def values
34
+ raise ::Watir::Exception::WatirException, "method should be implemented"
35
+ end
36
+
37
+ # returns Radio||Checkbox from a group that
38
+ # has specific value attribute
39
+ def get_by_value value
40
+ raise ::Watir::Exception::WatirException, "method should be implemented"
41
+ end
42
+
43
+ # returns Radio||Checkbox from a group that
44
+ # occupies specifi position in a group
45
+ # WARNING: it is 1-based NOT 0-based
46
+ # the intention is to enumerate position staring with 1, the way
47
+ # customer would enumerate items
48
+ def get_by_position position
49
+ if (1..self.size).member? position
50
+ @o[position-1]
51
+ else
52
+ raise ::Watir::Exception::WatirException, "positon #{position} is out of range of size"
53
+ end
54
+ end
55
+ end
56
+
57
+ # for IE only
58
+ module RadioCheckGroup
59
+
60
+ def values
61
+ opts = []
62
+ @o.each {|r| opts << r.ole_object.invoke('value')}
63
+ return opts
64
+ end
65
+
66
+ def get_by_value value
67
+ if values.member? value
68
+ @o.find {|r| r.ole_object.invoke('value') == value}
69
+ else
70
+ raise ::Watir::Exception::WatirException, "value #{value} not found in hidden_values"
71
+ end
72
+ end
73
+ end
74
+
75
+ #for firefox and ie
76
+ module RadioGroupCommonWatir
77
+
78
+ # Only one radio in RadioGroup can be selected just like
79
+ # only one option in single select list box can be selected.
80
+ # this method is a bit gratuitious because it will always return array
81
+ # with one item but it's here to keep the plural for compatibility with
82
+ # CheckboxGroup or SelectList. if at some point your page object gets changed from RadioGroup
83
+ # to SelectList your tests will not have to change
84
+ def selected_values
85
+ selected_value.to_a
86
+ end
87
+
88
+
89
+ # returns radio that is selected.
90
+ # there can only be one radio selected.
91
+ # in the event that none is selected it returns nil
92
+ # see selected_value commentary
93
+ def selected_radio
94
+ @o.find {|r| r.isSet?}
95
+ end
96
+ end
97
+
98
+ # radios that share the same :name attribute form a RadioGroup.
99
+ # RadioGroup semantically behaves like single select list box
100
+ # usage: this class is accessed by Watir::Container#radio_group
101
+ # RadioGroup semantically behaves like single select list box.
102
+ #
103
+ # per HTML401: -
104
+ # "If no radio button in a set sharing the same control name
105
+ # is initially 'on', user agent behavior for choosing which
106
+ # control is initially 'on' is undefined
107
+ #
108
+ # The idea of having all radios off makes no sense but in the wild you can see lots of examples.
109
+ # it would be better to just have a single select list box with no items selected instead of radios.
110
+ # The point of having radios is that at least one radio is 'ON' providing a default value for the group
111
+ #
112
+ # @browser = Watir::IE.attach :url, //
113
+ # @browser.radio_group('food') # => RadioGroup with :name, 'food'
114
+ #
115
+ class RadioGroup
116
+
117
+ include RadioCheckGroupCommonWatir
118
+ include RadioCheckGroup
119
+ include RadioGroupCommonWatir
120
+
121
+ def initialize(container, name)
122
+ @container = container
123
+ @name = name
124
+ @o = @container.radios.find_all {|r| r.name == @name}
125
+ end
126
+
127
+ # which value is selected?. returns value text as string
128
+ # So per HTML401 spec I am not sure if we should ever have empyt array returned here
129
+ # if you do get empty array then I would speak with developers to fix this and explicity
130
+ # provide checked for one radio on page load.
131
+ def selected_value
132
+ selected_radio.ole_object.invoke('value')
133
+ end
134
+
135
+ # in the absence of visible text like in select list we treat value
136
+ # as a selected text invisible to the user
137
+ alias selected selected_value
138
+
139
+ end
140
+
141
+
142
+ module CheckboxGroupCommonWatir
143
+
144
+ # returns selected checkboxex as array
145
+ # [] = nothing selected
146
+ # [checkbox, checkbox] = checkboxes that are selected.
147
+ def selected_checkboxes
148
+ @o.select {|cb| cb.isSet?}
149
+ end
150
+
151
+ # convinience method as a filter for select_values
152
+ # returns:
153
+ # nil => when no checkbox is set
154
+ # 'value' => if one checkbox is set
155
+ # or bypass filter and return selected_values array
156
+ def selected_value
157
+ arr = selected_values
158
+ case arr.size
159
+ when 0 then nil
160
+ when 1 then arr[0]
161
+ else arr
162
+ end
163
+ end
164
+
165
+ # in case of checkbox there are no visible text items.
166
+ # We rely on value attributes that must be present
167
+ # to differentiate the checkbox in a group
168
+ # compare to SelectList where selected returns selected_item
169
+ alias selected selected_value
170
+
171
+ end
172
+
173
+ # Checkbox group semantically behaves like multi select list box.
174
+ # each checkbox is a menu item groupped by the common attribute :name
175
+ # each checkbox can be off initially (a bit different semantics than RadioGroup)
176
+ class CheckboxGroup
177
+
178
+ include RadioCheckGroupCommonWatir
179
+ include RadioCheckGroup
180
+ include CheckboxGroupCommonWatir
181
+
182
+ def initialize(container, name)
183
+ @container = container
184
+ @name = name
185
+ @o = @container.checkboxes.find_all {|cb| cb.name == @name}
186
+ end
187
+
188
+ # returns array of value attributes. Each Checkbox in a group
189
+ # has a value which is invisible to the user
190
+ def selected_values
191
+ values = []
192
+ selected_checkboxes.each do |cb|
193
+ values << cb.ole_object.invoke('value')
194
+ end
195
+ return values
196
+ end
197
+ end
198
+
199
+
200
+ module Container
201
+ def radio_group(name)
202
+ RadioGroup.new(self, name)
203
+ end
204
+
205
+ def checkbox_group(name)
206
+ CheckboxGroup.new(self, name)
207
+ end
208
+ end
209
+
210
+ class RadioCheckCommon
211
+ alias set? isSet?
212
+ end
213
+
214
+ # these methods work for IE and for Firefox
215
+ module SelectListCommonWatir
216
+
217
+ # selected_items examples
218
+ # [] => nothing selected
219
+ # ['item'] => if one selected
220
+ # ['item1', 'item2', 'item3'] => several items selected
221
+ def selected_items
222
+ getSelectedItems
223
+ end
224
+
225
+ # selected_item is a convenience filter for selected_items
226
+ # returns
227
+ # nil if no options selected
228
+ # 'text' string if one option selected.
229
+ # or selected_items if more than one option selected
230
+ def selected_item
231
+ arr = selected_items # limit to one mehtod call
232
+ case arr.size
233
+ when 0 then nil
234
+ when 1 then arr[0]
235
+ else arr
236
+ end
237
+ end
238
+
239
+
240
+ # for selecte lists by default we return the text of an option
241
+ # compare to selected in RadioGroup or Checkbox group which return the
242
+ # value attributes since there is no visible text for the user
243
+ alias selected selected_item
244
+
245
+ # set :value or :text
246
+ def _set(how, what)
247
+ if what.kind_of? Array
248
+ what.each { |item| _set(how,item)} # call self with individual item
249
+ else
250
+ if what.kind_of? Fixnum # if by position then translate to set by text
251
+ if (0..items.size).member? what
252
+ _set :text, items[what-1]
253
+ else
254
+ raise ::Watir::Exception::WatirException, "number #{item} is out of range of item count"
255
+ end
256
+ else
257
+ select_item_in_select_list(how, what) #finally as :value or :text
258
+ end
259
+ end
260
+
261
+ end
262
+ private :_set
263
+
264
+
265
+
266
+
267
+ # similar to selected_items but returns array of option value attributes
268
+ def selected_values
269
+ assert_exists
270
+ arr = []
271
+ @o.each do |thisItem|
272
+ if thisItem.selected
273
+ arr << thisItem.value
274
+ end
275
+ end
276
+ return arr
277
+ end
278
+
279
+ # convinience method as a filter for select_values
280
+ # returns:
281
+ # nil for nothing selected.
282
+ # single value if only once selected or just
283
+ # or returns selected_values
284
+ def selected_value
285
+ arr = selected_values
286
+ case arr.size
287
+ when 0 then nil
288
+ when 1 then arr[0]
289
+ else arr
290
+ end
291
+ end
292
+
293
+
294
+ end
295
+
296
+ # SelectList acts like RadioGroup or CheckboxGroup
297
+ # They all have options to select
298
+ # There are two kinds of SelectLists. SingleSelect and MultiSelect
299
+ # SelectList presents user with visible items to select from.
300
+ # Each Item has a visible :text and invisible :value attributes
301
+ # (sometimes :value attributes are missing)
302
+ #
303
+ # In Watirloo
304
+ # The invisible :value attributes of options we call :values
305
+ # The visible :text of options we call :items
306
+ # The selected items as visible text we call :selected
307
+ # The selected items as values we call :selected_values
308
+ #
309
+ # example of single select list
310
+ #
311
+ # <select name="controlname">
312
+ # <option value="opt0"></option>
313
+ # <option value="opt1">item1</option>
314
+ # <option value="opt2" selected>item2</option>
315
+ # </select>
316
+ #
317
+ # items => ['', 'item1', 'item2']
318
+ # values => ['opt0','opt1', 'opt2']
319
+ # selected => ['item2']
320
+ # selected_values => ['opt2']
321
+ #
322
+ # example of multi select list
323
+ #
324
+ # <select name="controlname" multiple size=2>
325
+ # <option value="o1">item1
326
+ # <option value="o2" selected>item2
327
+ # <option value="o3" selected>item3
328
+ # </select>
329
+ #
330
+ # items => ['item1', 'item2', 'item3']
331
+ # values => ['o1','o2','o3']
332
+ # selected => ['item2', 'item3']
333
+ # selected_values => ['o2', 'o3']
334
+ #
335
+ class SelectList
336
+
337
+ include SelectListCommonWatir
338
+
339
+
340
+ # accepts one text item or array of text items. if array then sets one after another.
341
+ # For single select lists the last item in array wins
342
+ #
343
+ # examples
344
+ # select_list.set 'bla' # => single option text
345
+ # select_list.set ['bla','foo','gugu'] # => set 3 options by text. If
346
+ # this is a single select list box it will set each value in turn
347
+ # select_list set 1 # => set the first option in a list
348
+ # select_list.set [1,3,5] => set the first, third and fith options
349
+ def set(item)
350
+ _set(:text, item)
351
+ end
352
+
353
+ # set item by the option value attribute. if array then set one after anohter.
354
+ # see examples in set method
355
+ def set_value(value)
356
+ _set(:value, value)
357
+ end
358
+
359
+ # returns array of value attributes
360
+ # each option usually has a value attribute
361
+ # which is hidden to the person viewing the page
362
+ def values
363
+ a = []
364
+ attribute_value('options').each do |item|
365
+ a << item.value
366
+ end
367
+ return a
368
+ end
369
+
370
+ alias clear clearSelection
371
+
372
+ # alias, items or contents return the same visible text items
373
+ alias items getAllContents
374
+
375
+ end
376
+ end