iu-test-factory 0.5.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,133 @@
1
+ # Copyright 2012-2014 The rSmart Group, Inc.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ class Time
16
+
17
+ # Using the :year_range option (or no option), this method creates a
18
+ # Time object of a random value, within
19
+ # the year range specified (default is 5 years in the past).
20
+ #
21
+ # Using the :series option, this method returns an array
22
+ # containing a randomized Time object as its first element (limited by
23
+ # the specified :year_range value). Subsequent elements will be Time objects
24
+ # with values putting them later than the prior element, within the specified
25
+ # range value (see examples).
26
+ #
27
+ # Usage Examples:
28
+ # @example
29
+ # a random date...
30
+ # ?> Time.random
31
+ # => Tue Aug 05 00:00:00 EDT 2007
32
+ #
33
+ # birthdays, anyone?...
34
+ # 5.times { p Time.random(:year_range=>80) }
35
+ # Wed Feb 06 00:00:00 EDT 1974
36
+ # Tue Dec 22 00:00:00 EST 1992
37
+ # Fri Apr 14 00:00:00 EWT 1944
38
+ # Thu Jul 01 00:00:00 EDT 1993
39
+ # Wed Oct 02 00:00:00 EDT 2002
40
+ #
41
+ # A series of dates are useful for account-related info...
42
+ # ?> Time.random(:series=>[20.days, 3.years])
43
+ # => [Sat Jan 22 00:00:00 EST 2005,
44
+ # Sat Jan 29 12:58:45 EST 2005,
45
+ # Fri Sep 08 09:34:58 EDT 2006]
46
+ #
47
+ # or maybe to simulate events during an hour?...
48
+ # ?> Time.random(:series=>[1.hour,1.hour,1.hour])
49
+ # => [Wed Apr 21 00:00:00 EDT 2004,
50
+ # Wed Apr 21 00:45:59 EDT 2004,
51
+ # Wed Apr 21 01:02:47 EDT 2004,
52
+ # Wed Apr 21 01:31:00 EDT 2004]
53
+ #
54
+ def self.random(params={})
55
+ years_back = params[:year_range] || 5
56
+ year = (rand * (years_back)).ceil + (Time.now.year - years_back)
57
+ month = (rand * 12).ceil
58
+ day = (rand * 31).ceil
59
+ series = [date = Time.local(year, month, day)]
60
+ if params[:series]
61
+ params[:series].each do |some_time_after|
62
+ series << series.last + (rand * some_time_after).ceil
63
+ end
64
+ return series
65
+ end
66
+ date
67
+ end
68
+
69
+ end # Time
70
+
71
+ module Enumerable
72
+
73
+ # Use for getting a natural sort order instead of the ASCII
74
+ # sort order.
75
+ #
76
+ def alphabetize
77
+ sort { |a, b| grouped_compare(a, b) }
78
+ end
79
+
80
+ # Use for sorting an Enumerable object in place.
81
+ #
82
+ def alphabetize!
83
+ sort! { |a, b| grouped_compare(a, b) }
84
+ end
85
+
86
+ private
87
+
88
+ def grouped_compare(a, b)
89
+ loop {
90
+ a_chunk, a = extract_alpha_or_number_group(a)
91
+ b_chunk, b = extract_alpha_or_number_group(b)
92
+ ret = a_chunk <=> b_chunk
93
+ return -1 if a_chunk == ''
94
+ return ret if ret != 0
95
+ }
96
+ end
97
+
98
+ def extract_alpha_or_number_group(item)
99
+ test_item = item.downcase
100
+ matchdata = /([a-z]+|[\d]+)/.match(test_item)
101
+ if matchdata.nil?
102
+ ["", ""]
103
+ else
104
+ [matchdata[0], test_item = test_item[matchdata.offset(0)[1] .. -1]]
105
+ end
106
+ end
107
+
108
+ end # Enumerable
109
+
110
+ class Numeric
111
+
112
+ # Converts a number object to a string containing commas every 3rd digit. Adds
113
+ # trailing zeroes if necessary to match round currency amounts.
114
+ def commas
115
+ self.to_s =~ /([^\.]*)(\..*)?/
116
+ int, dec = $1.reverse, $2 ? ('%.2f' % $2).to_s[/.\d+$/] : ".00"
117
+ while int.gsub!(/(,|\.|^)(\d{3})(\d)/, '\1\2,\3')
118
+ end
119
+ int.reverse + dec
120
+ end
121
+
122
+ end # Numeric
123
+
124
+ class String
125
+
126
+ # Used to remove commas and dollar signs from long number strings,
127
+ # then converting the result to a Float so that it
128
+ # can be used in calculations.
129
+ def groom
130
+ self.gsub(/[$,]/,'').to_f
131
+ end
132
+
133
+ end # String
@@ -0,0 +1,334 @@
1
+ # Copyright 2012-2014 The rSmart Group, Inc.
2
+
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # The Superclass for all of your data objects.
16
+ class DataFactory
17
+
18
+ include Foundry
19
+ extend Forwardable
20
+
21
+ # Since Data Objects are not "Marshallable", and they generally
22
+ # contain lots of types of data in their instance variables,
23
+ # we have this method. This will create and return a 'deep copy' of
24
+ # the data object as well as any and all nested data objects
25
+ # and collections it contains.
26
+ #
27
+ # Please note that this method will fail if you are putting
28
+ # Data Objects into Arrays or Hashes instead
29
+ # of into Collection classes
30
+ #
31
+ def data_object_copy
32
+ opts = {}
33
+ self.instance_variables.each do |var|
34
+ key = var.to_s.gsub('@','').to_sym
35
+ orig_val = instance_variable_get var
36
+ opts[key] = case
37
+ when orig_val.kind_of?(CollectionsFactory)
38
+ orig_val.copy
39
+ when orig_val.instance_of?(Array) || orig_val.instance_of?(Hash)
40
+ begin
41
+ Marshal::load(Marshal.dump(orig_val))
42
+ rescue TypeError
43
+ raise %{\nKey: #{key.inspect}\nValue: #{orig_val.inspect}\nClass: #{orig_val.class}\n\nThe copying of the Data Object has thrown a TypeError,\nwhich means the object detailed above is not "Marshallable".\nThe most likely cause is that you have put\na Data Object inside an\nArray or Hash.\nIf possible, put the Data Object into a Collection.\n\n}
44
+ end
45
+ when orig_val.kind_of?(DataFactory)
46
+ orig_val.data_object_copy
47
+ else
48
+ orig_val
49
+ end
50
+ end
51
+ self.class.new(@browser, opts)
52
+ end
53
+
54
+ # Add this to the bottom of your Data Object's initialize method.
55
+ # This method does 2 things:
56
+ # 1) Converts the contents of the hash into the class's instance variables.
57
+ # 2) Grabs the names of your collection class instance variables and stores
58
+ # them in an Array. This is to allow for the data object class to send
59
+ # any needed updates to its children. See #notify_coolections for more
60
+ # details.
61
+ # @param hash [Hash] Contains all options required for creating the needed Data Object
62
+ #
63
+ def set_options(hash)
64
+ @collections ||= []
65
+ hash.each do |key, value|
66
+ instance_variable_set("@#{key}", value)
67
+ @collections << key if value.kind_of?(CollectionsFactory)
68
+ end
69
+ end
70
+ alias update_options set_options
71
+
72
+ # Use for setting a data object's class instance variable as a nested collection class.
73
+ #
74
+ # This method assumes your collection class name ends with "Collection". Therefore,
75
+ # the string you pass in its parameter is the first part of the class name.
76
+ #
77
+ # E.g., your collection class is called "DataObjectCollection", so, inside your
78
+ # parent object's defaults, you'd set the instance variable like this:
79
+ #
80
+ # @example
81
+ # data_objects: collection('DataObject')
82
+ #
83
+ def collection(name)
84
+ Kernel.const_get("#{name}Collection").new(@browser)
85
+ end
86
+
87
+ # Items passed to this method are checked to ensure that the associated class instance variable
88
+ # is not nil. If it is, the script is aborted and an error is thrown. Use symbols separated
89
+ # by commas with this method. The symbol(s) should exactly match the name of the instance
90
+ # variable that must not be empty.
91
+ #
92
+ # NOTE: Currently this is backwards compatible with prior versions, which took the instance
93
+ # variables directly in the parameter. This backwards compatibility will be removed in
94
+ # some future update of the gem.
95
+ #
96
+ # @param elements [Array] the list of items that are required.
97
+ #
98
+ # @example
99
+ # requires :site, :assignment, :document_id
100
+ #
101
+ def requires(*elements)
102
+ elements.each do |inst_var|
103
+ if inst_var.kind_of? Symbol
104
+ string="@#{inst_var.to_s}"
105
+ if instance_variable_get(string)==nil
106
+ raise "You've neglected to define a required variable for your #{self.class}.\n\nPlease ensure you always specify a value for #{string} when you create the data object."
107
+ end
108
+ elsif inst_var.kind_of? String
109
+ warn "<<<<WARNING!>>>>\n\nPlease update the requires method in your\n#{self.class} class to refer to symbols\ninstead of directly referencing the class'\ninstance variables.\n\n Example:\n\n This...\n requires @document_id\n Should be updated to...\n requires :document_id\n\nIn future versions of TestFactory the 'requires'\nmethod will only support symbolized references\nto the instance variables. The backwards\ncompatibility will be removed.\n\n<<<<WARNING!>>>>"
110
+ elsif inst_var==nil
111
+ raise "You've neglected to define a required variable for your #{self.class}.\n\n<<<<WARNING!>>>>\n\nPlease update the requires method in your #{self} class to refer to symbols\ninstead of directly referencing the class'\ninstance variables.\n\nIn future versions of TestFactory the 'requires' method\nwill only support symbolized references\nto the instance variables. The backwards\ncompatibility will be removed.\n\n<<<<WARNING!>>>>"
112
+ end
113
+ end
114
+ end
115
+
116
+ # A shortcut method for filling out fields on a page. The
117
+ # method's first parameter is the page class that contains the fields
118
+ # you want to fill out. That is followed by the list of field name(s)
119
+ # (as Symbols).
120
+ #
121
+ # This method has a number of requirements:
122
+ #
123
+ # 1) The field name and the instance variable name in your data object
124
+ # must be identical. For this reason, this method can only
125
+ # be used in your data objects' create methods.
126
+ #
127
+ # 2) Your checkbox and radio button data object instance variables are
128
+ # either +nil+, +:set+, or +:clear+. Any other values will not be handled
129
+ # correctly.
130
+ #
131
+ # 3) Since the listed fields get filled out in random order, be sure that
132
+ # this is okay in the context of your page--in other words, if field A
133
+ # needs to be specified before field B then having them both in your
134
+ # fill_out step would be inappropriate. If you need a specific order,
135
+ # use #ordered_fill instead.
136
+ #
137
+ # 4) This method supports text fields, select lists, check boxes, and
138
+ # radio buttons, but only if their element definitions don't take a
139
+ # parameter. Please use the +#fill_out_item+ with elements that do need
140
+ # a parameter defined.
141
+ #
142
+ # @example
143
+ # on PageClass do |page|
144
+ # fill_out page, :text_field_name, :radio_name, :select_list_name, :checkbox_name
145
+ # end
146
+ #
147
+ def fill_out(page, *fields)
148
+ f_o_i true, nil, page, *fields
149
+ end
150
+
151
+ # Use when you need to specify the order that the fields should be
152
+ # updated.
153
+ #
154
+ # @example
155
+ # on PageClass do |page|
156
+ # ordered_fill page, :text_field_name, :radio_name, :select_list_name, :checkbox_name
157
+ # end
158
+ #
159
+ def ordered_fill(page, *fields)
160
+ f_o_i false, nil, page, *fields
161
+ end
162
+
163
+ # Same as #fill_out, but used with methods that take a
164
+ # parameter to identify the target element...
165
+ #
166
+ # @example
167
+ # on PageClass do |page|
168
+ # fill_out_item 'Joe Schmoe', page, :text_field_name, :radio_name, :select_list_name, :checkbox_name
169
+ # end
170
+ #
171
+ def fill_out_item(name, page, *fields)
172
+ f_o_i true, name, page, *fields
173
+ end
174
+
175
+ # Use instead of #fill_out_item when you need to
176
+ # specify the order that the fields should be
177
+ # updated.
178
+ #
179
+ # @example
180
+ # on PageClass do |page|
181
+ # ordered_item_fill 'Joe Schmoe', page, :text_field_name, :radio_name, :select_list_name, :checkbox_name
182
+ # end
183
+ #
184
+ def ordered_item_fill(name, page, *fields)
185
+ f_o_i false, name, page, *fields
186
+ end
187
+
188
+ # Equivalent to #ordered_fill, except that it's used
189
+ # in the context of a Data Object's #edit method(s). As such, it
190
+ # requires the #edit method's hash to be passed as its own
191
+ # first parameter.
192
+ #
193
+ # @example
194
+ # on PageClass do |page|
195
+ # edit_fields opts, page, :text_field_name, :radio_name, :select_list_name, :checkbox_name
196
+ # end
197
+ #
198
+ def edit_fields(opts, page, *fields)
199
+ edit_item_fields opts, nil, page, *fields
200
+ end
201
+
202
+ # Equivalent to #ordered_item_fill, except that it's used
203
+ # in the context of a Data Object's #edit method(s). As such, it
204
+ # requires the #edit method's hash to be passed as its own
205
+ # first parameter.
206
+ #
207
+ # @example
208
+ # on PageClass do |page|
209
+ # edit_item_fields opts, 'Joe Schmoe', page, :text_field_name, :radio_name, :select_list_name, :checkbox_name
210
+ # end
211
+ #
212
+ def edit_item_fields(opts, name, page, *fields)
213
+ parse_fields(opts, name, page, *fields)
214
+ end
215
+
216
+ # This is a specialized method for use with any select list boxes
217
+ # that exist in the site you're testing and will contain
218
+ # unpredictable default values.
219
+ #
220
+ # Admittedly, this is a bit unusual, but one example would be
221
+ # be a "due date" list that changes its default selection based
222
+ # on today's date. You're going to want to do one of two things
223
+ # with that select list:
224
+ #
225
+ # 1) Retrieve and store the select list's value
226
+ # 2) Specify a custom value to select
227
+ #
228
+ # Enter: +#get_or_select!+
229
+ #
230
+ # Assuming you just want to store the default value, then your
231
+ # Data Object's instance variable for the field will--initially--be
232
+ # nil. In that case, +#get_or_select!+ will grab the select list's
233
+ # current value and store it in your instance variable.
234
+ #
235
+ # On the other hand, if you want to update that field with your
236
+ # custom value, then your instance variable will not be nil, so
237
+ # +#get_or_select!+ will take that value and use it to update the
238
+ # select list.
239
+ #
240
+ # Note that this method *only* works with select lists that take
241
+ # a single selection. Multi-selects are not supported.
242
+ #
243
+ # Also note that the first parameter is *not* the instance variable
244
+ # you need to use/update. It is a *symbol* that otherwise matches
245
+ # the instance variable.
246
+ #
247
+ # @param inst_var_sym [Symbol] A Symbol that _must_ match the instance variable that
248
+ # will either be set or be used to update the page
249
+ # @param select_list [Watir::Select] The relevant select list element on the page
250
+ #
251
+ # @example
252
+ # get_or_select! :@num_resubmissions, page.num_resubmissions
253
+ #
254
+ def get_or_select!(inst_var_sym, select_list)
255
+ value = instance_variable_get inst_var_sym
256
+ if value==nil
257
+ instance_variable_set inst_var_sym, select_list.selected_options[0].text
258
+ else
259
+ select_list.select value
260
+ end
261
+ end
262
+
263
+ # This method accomplishes the same thing as #get_or_select! but
264
+ # is used specifically when the instance variable being used/updated
265
+ # is a Hash and you only need to update one of its key/value pairs.
266
+ #
267
+ # Pay close attention to the syntax differences between
268
+ # this method and #get_or_select!
269
+ #
270
+ # First, note that the returned value of this method must be explicitly
271
+ # passed to the relevant key in the Hash instance variable. Note also that, unlike
272
+ # #get_or_select!, this method does *not* take a symbolized representation
273
+ # of the instance variable.
274
+ #
275
+ # @example
276
+ # @open[:day] = get_or_select(@open[:day], page.open_day)
277
+ #
278
+ def get_or_select(hash_inst_var, select_list)
279
+ if hash_inst_var==nil
280
+ select_list.selected_options[0].text
281
+ else
282
+ select_list.select hash_inst_var
283
+ hash_inst_var
284
+ end
285
+ end
286
+
287
+ # Define this method in your data object when
288
+ # it has a parent, and that parent
289
+ # may periodically need to send
290
+ # it updated information about itself.
291
+ #
292
+ def update_from_parent(update)
293
+ raise %{
294
+ This method must be implemented in your data object
295
+ class if you plan to pass updates from a
296
+ parent object to the members of its
297
+ collections.
298
+ }
299
+ end
300
+
301
+ # =======
302
+ private
303
+ # =======
304
+
305
+ # Do not use this method directly.
306
+ #
307
+ def f_o_i(shuffle, name, page, *fields)
308
+ shuffle ? fields.shuffle! : fields
309
+ parse_fields(nil, name, page, *fields)
310
+ end
311
+
312
+ # Do not use this method directly.
313
+ #
314
+ def parse_fields(opts, name, page, *fields)
315
+ fields.each do |field|
316
+ lmnt = page.send(*[field, name].compact)
317
+ var = opts.nil? ? instance_variable_get("@#{field}") : opts[field]
318
+ lmnt.class.to_s == 'Watir::Select' ? lmnt.pick!(var) : lmnt.fit(var)
319
+ end
320
+ end
321
+
322
+ # Use this method in conjunction with your nested
323
+ # collection classes. The collections will notify
324
+ # their members about the passed parameters.
325
+ #
326
+ # Note: You must write a custom #update_from_parent
327
+ # method in your data object that will know what to
328
+ # do with the parameter(s) passed to it.
329
+ #
330
+ def notify_collections *updates
331
+ @collections.each {|coll| instance_variable_get("@#{coll}").notify_members *updates }
332
+ end
333
+
334
+ end