taft 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -3
  3. data/examples/ruby/rs/framework/red_sky/api_helpers/general.rb +140 -0
  4. data/examples/ruby/rs/framework/red_sky/api_helpers/rest.rb +249 -0
  5. data/examples/ruby/rs/framework/red_sky/ui_helpers/ui_general.rb +36 -0
  6. data/examples/ruby/rs/framework/red_sky/watir/custom/all.rb +4 -0
  7. data/examples/ruby/rs/framework/red_sky/watir/custom/rs_custom.rb +32 -0
  8. data/examples/ruby/rs/framework/red_sky/watir/flows/flow_objects.rb +466 -0
  9. data/examples/ruby/rs/framework/red_sky/watir/flows/rs_flow_names.rb +15 -0
  10. data/examples/ruby/rs/framework/red_sky/watir/flows/rs_flows.rb +117 -0
  11. data/examples/ruby/rs/framework/red_sky/watir/pages/page_objects.rb +166 -0
  12. data/examples/ruby/rs/framework/red_sky/watir/pages/rs_pages.rb +68 -0
  13. data/examples/ruby/rs/framework/red_sky.rb +12 -0
  14. data/examples/ruby/rs/lib/config/red_sky_config.rb +27 -0
  15. data/examples/ruby/rs/lib/config/runtime_constants.rb +20 -0
  16. data/examples/ruby/rs/lib/red_sky_test_case.rb +218 -0
  17. data/examples/ruby/rs/tests/v1/tc_r001_01_an_example_test.rb +112 -0
  18. data/examples/ruby/rs/tests/v1/tc_r001_01_google_search.rb +64 -0
  19. data/lib/taft_files/framework/zznamezz/api_helpers/general.rb +11 -11
  20. data/lib/taft_files/framework/zznamezz/ui_helpers/ui_general.rb +26 -0
  21. data/lib/taft_files/framework/zznamezz/watir/custom/all.rb +4 -0
  22. data/lib/taft_files/framework/zznamezz/watir/custom/xxabbrevxx_custom.rb +32 -0
  23. data/lib/taft_files/framework/zznamezz/watir/flows/flow_objects.rb +466 -0
  24. data/lib/taft_files/framework/zznamezz/watir/flows/xxabbrevxx_flow_names.rb +15 -0
  25. data/lib/taft_files/framework/zznamezz/watir/flows/xxabbrevxx_flows.rb +117 -0
  26. data/lib/taft_files/framework/zznamezz/watir/pages/page_objects.rb +166 -0
  27. data/lib/taft_files/framework/zznamezz/watir/pages/xxabbrevxx_pages.rb +101 -0
  28. data/lib/taft_files/framework/zznamezz.rb +4 -0
  29. data/lib/taft_files/lib/config/runtime_constants.rb +5 -1
  30. data/lib/taft_files/lib/config/zznamezz_config.rb +7 -2
  31. data/lib/taft_files/lib/zznamezz_test_case.rb +43 -42
  32. data/lib/taft_files/tests/v1/tc_r001_01_an_example_test.rb +1 -1
  33. data/taft.gemspec +4 -4
  34. metadata +28 -5
@@ -0,0 +1,466 @@
1
+ # A class defining the series of Flow classes & helpers
2
+
3
+ # TODO define some base class/classes. Must be scope here for consolidation through inheritance?
4
+
5
+ #require 'minitest/unit' # needed? # TODO use test-unit, or convert everything else to minitest
6
+
7
+ #class FlowObjects
8
+ # attr_accessor :flows
9
+ #
10
+ # def initialize
11
+ # @flows = [] # an array of Flow objects
12
+ # end
13
+ #
14
+ # def add_flow(flow)
15
+ # @flows << flow
16
+ # end
17
+ #
18
+ # # TODO needed?
19
+ # def ==(o)
20
+ # end
21
+ #
22
+ # # TODO needed?
23
+ # def to_s
24
+ # end
25
+ #
26
+ #end
27
+
28
+ # A class defining a flow. This contains the fields to be interacted with, in their proper sequence, with valid or
29
+ # invalid values
30
+ class Flow
31
+
32
+ attr_accessor :name
33
+ attr_accessor :flow_map # some collection of Flow, FlowField & other Flow-type objects # TODO
34
+
35
+ def initialize(name)
36
+ @name = name
37
+ @flow_map = []
38
+ end
39
+
40
+ def add(flow_item)
41
+ @flow_map << flow_item
42
+ end
43
+
44
+ # TODO needed?
45
+ def ==(o)
46
+ end
47
+
48
+ def to_s
49
+ s = ""
50
+ s += "Flow name : #{@name}. Flow map size : #{@flow_map.size}. Flow items :"
51
+ @flow_map.each {|f| s += "\n\t#{f.to_s}" }
52
+ s
53
+ end
54
+
55
+ # Executes the flow
56
+ # Takes symbols which act as flags :
57
+ # success_sym - set to :success for a valid set of inputs, set to :fail for one or more of the inputs to be invalid and
58
+ # the flow to fail
59
+ # mandatory_fields_sym - set to :mandatory to only involve the mandatory fields, set to :all_fields to use all defined
60
+ # fields in the flow
61
+ # custom_values_hash - hash of values some/all of the fields must take (e.g. creating a new record with a specific foreign key)
62
+ # keys are field names (as defined in CeresFlows) in either symbol or string form, values are the values the fields should take
63
+ # TODO : don't want to have to pass in browser each time
64
+ def execute(browser, custom_values_hash = {}, success_sym = :success, mandatory_fields_sym = :mandatory)
65
+ report = nil # if report is still nil at the end of the method, generate a dummy one
66
+ return_value = nil # default value that will be fed into the report # TODO needs to be an array? Or hold multiple
67
+ # values?
68
+ # pass = true
69
+ # all = true
70
+ case success_sym
71
+ when :success
72
+ pass = true
73
+ when :fail
74
+ pass = false
75
+ else
76
+ raise "Did not understand value '#{success_sym.inspect}' for success_sym"
77
+ end
78
+
79
+ case mandatory_fields_sym
80
+ when :mandatory
81
+ all = false
82
+ when :all_fields
83
+ all = true
84
+ else
85
+ raise "Did not understand value '#{mandatory_fields_sym.inspect}' for mandatory_fields_sym"
86
+ end
87
+
88
+ @flow_map.each do |flow_item|
89
+ case flow_item
90
+ when Flow # flows can contain sub-flows
91
+ flow_item.execute(browser, custom_values_hash, success_sym, mandatory_fields_sym) # call recursively
92
+ when FlowPrecondition
93
+ # TODO : find flow from name & execute it
94
+ flow_item.execute_precondition_flow(custom_values_hash)
95
+ when FlowLink
96
+ # debugging
97
+ # puts "Executing #{flow_item.name}"
98
+ # puts browser.url
99
+
100
+ browser.link(:id => flow_item.name).click
101
+
102
+ sleep 1 # TODO determine expected page, call page.wait_until_displayed
103
+ when FlowField
104
+ puts "Now executing field #{flow_item.to_s}"
105
+ next if all == false && flow_item.mandatory == false # skip optional fields if instructed
106
+
107
+ field = nil
108
+
109
+ # TODO : look up field identification parameters (e.g. :id => something) from field definition in rs_pages
110
+ # The linkage should be that the NAME of the rs_page field (e.g. page.add_field("role", :list, :id, "user_role") )
111
+ # matches the NAME of the flow_item. We therefore use flow_item.name to track down the field identification parameters
112
+ # stored in the field definition in rs_pages (in this example, :id, "user_role" )
113
+ # Until this is done, flow_item.name needs to match the rs_pages field ident param.
114
+
115
+ case flow_item.type
116
+ # TODO only have this case statement determine field type, then pass it into one eval line instead of one
117
+ # line per type
118
+ # Type field completion may differ by more than just their field type...
119
+
120
+ when :string
121
+ field = browser.text_field(:id => flow_item.name)
122
+ when :p
123
+ field = browser.p(:id => flow_item.name)
124
+ when :div
125
+ field = browser.div(:id => flow_item.name)
126
+ when :checkbox
127
+ field = browser.input(:id => flow_item.name)
128
+ when :list
129
+ # field = browser.select_list(:id => flow_item.name)
130
+ field = browser.select(:id => flow_item.name)
131
+ when :button
132
+ # buttons only have one function - to be pressed, not to be read or written to
133
+ browser.button(:id => flow_item.name).click
134
+ else
135
+ raise "Cannot execute #{flow_item.class} of type #{flow_item.type}"
136
+ end
137
+ case flow_item.operation
138
+ when :read
139
+ case flow_item.type
140
+ when :string, :checkbox, :list, :p, :div
141
+ # puts "value : #{field.value}"
142
+ # puts "text : #{field.text}"
143
+ return_value = field.value
144
+ return_value = field.text if return_value == "" # p needs .text
145
+ # puts "return_value : #{return_value}"
146
+ end
147
+ when :write
148
+ # get valid value from hash, if it has been specified
149
+ value = custom_values_hash[flow_item.name.to_sym].to_s # flow_item.name is defined in rs_flows to be a string; it is nicer if the custom hash keys are symbols, but we then need to convert them
150
+ value = flow_item.random_valid_value if value == nil
151
+ # value = flow_item.random_invalid_value if invalid # TODO enable
152
+ case flow_item.type
153
+ when :string, :checkbox
154
+ field.set(value)
155
+ when :list
156
+ field.select(value)
157
+ end
158
+ end
159
+ when FlowVerify
160
+ name = "verify"
161
+ verify_flow = Flow.new(name)
162
+ verify_flow.add(flow_item.flow_field)
163
+ # call recursively, have it generate a Report, perform validation against the Report
164
+ report = verify_flow.execute(browser)
165
+ #puts "report : #{report}"
166
+ value = report.value
167
+ flow_item.verify(value)
168
+ return_value = "FlowVerify passed : #{flow_item}"
169
+ when FlowReport # not yet in use - no FlowReports have been defined in rs_flows
170
+ report = flow_item.generate_report
171
+ else
172
+ raise "Cannot execute flow item of class #{flow_item.class}"
173
+ end # end case
174
+ end # end .each
175
+ if report == nil # if report is still nil at the end of the method, generate a dummy one
176
+ # return_value =
177
+ report = FlowReport.new(return_value)
178
+ end
179
+ report
180
+ end # end execute
181
+
182
+ end # end class
183
+
184
+ # A class defining a flow that must be executed as a precondition to the flow this object belongs to
185
+ class FlowPrecondition
186
+
187
+ attr_accessor :precondition_flow # precondition_flow is the name of the precondition flow that must be executed
188
+
189
+ def initialize(precondition_flow)
190
+ @precondition_flow = precondition_flow
191
+ end
192
+
193
+ def to_s
194
+ s = ""
195
+ s += "Precondition Flow : #{@precondition_flow}"
196
+ s
197
+ end
198
+
199
+ # TODO : need success & madatory field flags here?
200
+ def execute_precondition_flow(custom_values_hash = {})
201
+ # find flow from name
202
+ # TODO : flows all stored in @flow (CeresFlow.new()). Gaining access to this feels wrong...
203
+
204
+ # execute flow
205
+
206
+ end
207
+
208
+ end
209
+
210
+ # A class defining a navigation step that is needed as part of a flow. These assume that their navigation is done via
211
+ # links, not buttons/divs/etc.
212
+ class FlowLink
213
+
214
+ attr_accessor :name, :parent_page, :destination, :verification # parent_page is the page within which the desired link
215
+ # can be found
216
+ # destination is the name of the page that the browser will arrive at after performing this navigation.
217
+ # verification is a FlowVerify object that is defined such that it can only pass if it matches the defined field on the
218
+ # destination page
219
+ # TODO enable mechanism such that one can simply state flow.goto(destination) and all flows will be scanned for the
220
+ # flow that will take us there, then that flow will be executed.
221
+
222
+ def initialize(destination, name, parent_page)
223
+ @destination = destination
224
+ @name = name
225
+ @parent_page = parent_page
226
+ end
227
+
228
+ def to_s
229
+ s = ""
230
+ s += "Flow destination : #{@destination}. Link ID : #{@name}. Parent page : #{@parent_page}"
231
+ s
232
+ end
233
+
234
+ end
235
+
236
+ # A class defining a field that is interacted with in some way as part of a flow.
237
+ # Valid types: :string (a text field); :button (a button); :link (a link); :list (a select list); :checkbox (a checkbox)
238
+ # TODO still want link to be valid here? What about FlowLink?
239
+ class FlowField
240
+
241
+ attr_accessor :name, :type, :operation, :mandatory, :size, :custom_valid_value_definition, :custom_invalid_value_definition
242
+
243
+ def initialize(name, type = :string, operation = :write, mandatory = true, size = nil, custom_valid_value_definition = nil, custom_invalid_value_definition = nil)
244
+ raise "FlowField name must be a string" unless name.class == String
245
+ @name = name
246
+ @type = type
247
+ @operation = operation
248
+ raise "Cannot define FlowField #{@name} with operation of #{@operation.inspect}" unless @operation == :read || @operation == :write
249
+ @mandatory = mandatory
250
+ if size == nil
251
+ @size = get_default_size
252
+ else
253
+ @size = size
254
+ end
255
+
256
+ custom_valid_value_definition = nil
257
+ custom_invalid_value_definition = nil
258
+
259
+ check_valid_type
260
+
261
+ # TODO custom_valid_value_definition, etc
262
+ case custom_valid_value_definition
263
+ when NilClass
264
+ # take default valid field def based on @type
265
+ when Symbol
266
+ #run sub-case based on symbol
267
+ when Regexp # ?
268
+ # define method
269
+ when Array
270
+ @custom_valid_value_definition = custom_valid_value_definition
271
+ when block # ?
272
+ # define method
273
+ else
274
+ raise "Could not process custom_valid_value_definition specified for FlowField of name '#{@name}'"
275
+ end
276
+
277
+ case custom_invalid_value_definition
278
+ when NilClass
279
+ # take default valid field def based on @type
280
+ when Symbol
281
+ #run sub-case based on symbol
282
+ when Regexp # ?
283
+ # define method
284
+ when Block # ?
285
+ # define method
286
+ else
287
+ raise "Could not process custom_invalid_value_definition specified for FlowField of name '#{@name}'"
288
+ end
289
+ end
290
+
291
+ # Retrieves default sizes for fields
292
+ # Assumes @type is set
293
+ def get_default_size
294
+ size = 0
295
+ case @type
296
+ when :button, :link, :list, :checkbox
297
+ # do nothing
298
+ when :string
299
+ size = 32
300
+ when :p, :div # these will be read-only so this doesn't really matter
301
+ size = 4000
302
+ else
303
+ raise "#{@type} is not a valid type for FlowField"
304
+ end
305
+ size
306
+ end
307
+
308
+ # Valid types:
309
+ # :string (a text field);
310
+ # :button (a button);
311
+ # :link (a link);
312
+ # :list (a select list);
313
+ # :checkbox (a checkbox)
314
+ def check_valid_type
315
+ case @type
316
+ when :button, :link, :list, :checkbox
317
+ check_valid_size(0)
318
+ when :string, :p, :div
319
+ check_valid_size(@size) # TODO a pointless call - @size will compared against itself!
320
+ else
321
+ raise "#{@type} is not a valid type for FlowField"
322
+ end
323
+ end
324
+
325
+ # Raises unless the supplied size is greater or equal to @size
326
+ def check_valid_size(valid_size_for_type)
327
+ valid = false
328
+ case @size
329
+ when NilClass
330
+ valid = true if @size == nil
331
+ when TrueClass # possible?
332
+ valid = @size if something # TODO
333
+ when FalseClass # possible?
334
+ valid = @size if something # TODO
335
+ when Fixnum, String
336
+ valid = true if @size <= valid_size_for_type
337
+ end
338
+
339
+ raise "Defined size #{@size.inspect} for FlowField '#{@name}' is not valid for field of type '#{type}'" unless valid
340
+ end
341
+
342
+ # TODO needed?
343
+ def ==(o)
344
+ end
345
+
346
+ # TODO needed?
347
+ def to_s
348
+ s = ""
349
+ s += "Flow field : #{@name}. Type : #{@type}. Mandatory : #{@mandatory}. Size : #{@size}"
350
+ s
351
+ end
352
+
353
+ # Generate a random value based on its type
354
+ def random_valid_value
355
+ value = nil
356
+ case @type
357
+ when :string
358
+ value = rand_string(@size) # TODO : vary size of random string?
359
+ when :checkbox
360
+ # value = (rand(2) == 1) # TODO : need to have the object itself have defined what is a valid and invalid
361
+ # value
362
+ value = true # most checkboxes will want to be ticked, but it is plausable that the valid value for some of them
363
+ # is to be unticked
364
+ when :list
365
+ # TODO difficult - need to pick a random item from the list. How do we know its contents?
366
+ # Maybe pick a random number, not greater than the size of the list, then set by index/position?
367
+ if @custom_valid_value_definition != nil # if not nil, custom_valid_value_definition should be an array of the valid options
368
+ value = @custom_valid_value_definition.random
369
+ end
370
+ when :button
371
+ # do nothing
372
+ else
373
+ raise "Do not know how to generate a random valid value for FlowField of type #{@type}"
374
+ end
375
+ value
376
+ end
377
+
378
+ end
379
+
380
+ # A class defining a verification step
381
+ class FlowVerify
382
+
383
+ include Test::Unit::Assertions
384
+
385
+ attr_accessor :expected, :value_or_regex, :flow_field
386
+ # expected is a boolean for whether or not the verification is expected to succeed or fail
387
+ # value_or_regex is a string, number, boolean or regex
388
+ # flow_field is a FlowField object pointing to a field whose value value_or_regex must be used against
389
+
390
+ def initialize(expected, value_or_regex, flow_field)
391
+ @expected = expected
392
+ @value_or_regex = value_or_regex
393
+
394
+ @flow_field = flow_field
395
+ raise ":flow_field must be of class FlowField" unless @flow_field.class == FlowField
396
+ end
397
+
398
+ def to_s
399
+ s = ""
400
+ s += "Flow verifier : expected : #{@expected}. Value/regex : #{@value_or_regex.inspect}. Field : #{@flow_field}"
401
+ s
402
+ end
403
+
404
+ def verify(actual)
405
+ puts "now in verify for FlowVerify for field #{@flow_field} against value #{@value_or_regex}"
406
+
407
+ case @value_or_regex # case is better, leaves room for other options depending on class
408
+ when Regexp
409
+ match = !!(actual =~ @value_or_regex) # double-invert to convert to true-or-false
410
+ else
411
+ match = (actual == @value_or_regex)
412
+ end
413
+ if @expected
414
+ message = "FlowVerify failed. Expected the value to match #{@value_or_regex.inspect} but was actually #{actual.inspect}"
415
+ else
416
+ message = "FlowVerify failed. Expected the value #{@value_or_regex.inspect} to be different to the actual value of #{actual.inspect}"
417
+ end
418
+ puts "about to assert; #{actual.inspect} == #{@value_or_regex.inspect} => #{@expected == match}"
419
+ assert_equal(@expected, match, message)
420
+ puts "assertion passed"
421
+ end
422
+
423
+ end
424
+
425
+ # A class defining feedback to be returned after invoking the flow.
426
+ # TODO : rework this so that it knows how to gather the required information (?)
427
+ class FlowReport
428
+
429
+ # attr_accessor :success, :message, :value_hash_array
430
+ # # success is a boolean
431
+ # # message is a string
432
+ # # value_hash_array is an array of hashes, one per object/event/thing. Its keys are the object's fields, and the
433
+ # values
434
+ ## are their values
435
+ #
436
+ # def initialize(success, message, value_hash_array)
437
+ # @success = success
438
+ # @message = message
439
+ # @value_hash_array = value_hash_array
440
+ # end
441
+ #
442
+ # def to_s
443
+ # s = ""
444
+ # s += "Flow report : success? #{@success}. Message : #{@message}. Values : #{@value_hash_array}"
445
+ # s
446
+ # end
447
+
448
+ attr_accessor :value
449
+
450
+ def initialize(value = nil)
451
+ # do nothing?
452
+ @value = value
453
+ end
454
+
455
+ def to_s
456
+ s = ""
457
+ s += "Flow report : value : #{@value}"
458
+ s
459
+ end
460
+
461
+ def generate_report
462
+ #TODO
463
+ ""
464
+ end
465
+
466
+ end
@@ -0,0 +1,15 @@
1
+ # List of constants that store names of flows
2
+ # Names are stored as symbols
3
+
4
+ class RSFN
5
+
6
+ # Homepage
7
+ GOTO_HOMEPAGE = :goto_homepage
8
+
9
+ # Users
10
+ VIEW_ALL_USERS = :view_all_users
11
+ VIEW_USER = :view_user
12
+ CREATE_USER = :create_user
13
+ DELETE_USER = :delete_user
14
+
15
+ end
@@ -0,0 +1,117 @@
1
+ # A class defining the UI flows within RedSky, such that simple usecase-esq methods arise from them and can
2
+ # perform a series of UI actions (making a record, viewing & deleting, etc.)
3
+
4
+ # The intended use is that the test (or supporting framework) call @flow.flow_name, e.g. @flow.create_project, which will
5
+ # perform all of the actions of that flow. Parameters can be supplied to the call, which will change its behaviour
6
+
7
+ require_relative 'flow_objects'
8
+ require_relative 'rs_flow_names'
9
+
10
+ class RSFlows
11
+
12
+ attr_accessor :flows # array of Flow objects
13
+ attr_accessor :flow_names # array of names of known Flow objects
14
+
15
+
16
+ # Assembles all of the flows and stores them, ready for use
17
+ def initialize
18
+ @flows = [] # an array of Flow objects
19
+ @flow_names = []
20
+
21
+ # Simple nav flow to get to the homepage
22
+ flow = Flow.new(RSFN::GOTO_HOMEPAGE)
23
+ flow.add(FlowLink.new("goto_homepage", "rs_home_header_link", nil)) # no parent page, is valid from any page
24
+ add_flow(flow)
25
+
26
+ # View all users
27
+ flow = new_base_flow(RSFN::VIEW_ALL_USERS)
28
+ flow.add(FlowLink.new("all_users", "users_header_link", nil))
29
+ add_flow(flow)
30
+
31
+ # Create user
32
+ flow = new_base_flow(RSFN::CREATE_USER)
33
+ add_existing_flow_to_flow(flow, RSFN::VIEW_ALL_USERS)
34
+
35
+ flow.add(FlowLink.new("all_users", "users_header_link", nil))
36
+ flow.add(FlowLink.new("create_user", "new_user_link", "all_users"))
37
+ field_flow = Flow.new("create_user_fields")
38
+
39
+ field_flow.add(FlowField.new("user_name"))
40
+ field_flow.add(FlowField.new("user_role", :list, :write, true, nil, RedSkyConfig::ALL_USER_ROLES))
41
+
42
+ flow.add(field_flow)
43
+ flow.add(FlowField.new("save", :button))
44
+
45
+ message = FlowField.new("notice", :p, :read)
46
+ flow.add(FlowVerify.new(true, "User was successfully created.", message))
47
+
48
+ add_flow(flow)
49
+ end
50
+
51
+
52
+ ##############################################################################
53
+
54
+
55
+ def method_missing(name, *args, &block)
56
+ puts "RSFlows method_missing called; name = #{name.inspect}; #{name.class}"
57
+
58
+ if flow_known(name)
59
+ puts "Flow #{name} is known"
60
+ # TODO define a whole bunch of methods and then perform them
61
+ # If args and/or block have been provided, process them. E.g. one arg could be a trigger to perform the flow with
62
+ # invalid values # TODO : is that the best way of doing that?
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ def add_flow(flow)
69
+ @flows << flow
70
+ @flow_names << flow.name
71
+ end
72
+
73
+ # TODO needed?
74
+ def ==(o)
75
+ end
76
+
77
+ # TODO needed?
78
+ def to_s
79
+ s = ""
80
+ s += "#{@flows.size} flows defined. Names :"
81
+ @flow_names.each {|f| s += "\n#{f}" }
82
+ s
83
+ end
84
+
85
+ # Will convert name to a string
86
+ def flow_known?(name)
87
+ @flow_names.include?(name)
88
+ end
89
+
90
+ # Retrieves the specific flow; raises if it cannot be found
91
+ # Will convert name to a string
92
+ def find(name)
93
+ raise "Could not locate flow '#{name}'" unless flow_known?(name)
94
+ @flows[@flow_names.index(name)]
95
+ end
96
+
97
+ # Finds & executes a flow
98
+ def find_and_execute(browser, name, custom_values_hash = {}, success_sym = :success, mandatory_fields_sym = :mandatory)
99
+ f = find(name)
100
+ f.execute(browser, custom_values_hash, success_sym, mandatory_fields_sym)
101
+ end
102
+
103
+ # Adds an already-existing flow to the supplied flow
104
+ def add_existing_flow_to_flow(new_flow, existing_flow_name)
105
+ # TODO : need deduplication mechanism so that a flow doesn't gain two/more duplicate flow items in a row (e.g. two calls to GOTO_HOMEPAGE in a row)
106
+ new_flow.add(find(existing_flow_name))
107
+ end
108
+
109
+ # Shortcut to define a new flow with standard prerequisite flows already added
110
+ # Cannot be called until the flows that are to be added have been defined
111
+ def new_base_flow(name)
112
+ flow = Flow.new(name)
113
+ add_existing_flow_to_flow(flow, RSFN::GOTO_HOMEPAGE)
114
+ flow
115
+ end
116
+
117
+ end