kuali-test-factory 0.5.3.1
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.
- checksums.yaml +7 -0
- data/COPYING +68 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +33 -0
- data/LICENSE +68 -0
- data/README.md +241 -0
- data/lib/test-factory.rb +18 -0
- data/lib/test-factory/collections_factory.rb +67 -0
- data/lib/test-factory/core_ext.rb +133 -0
- data/lib/test-factory/data_factory.rb +334 -0
- data/lib/test-factory/date_factory.rb +158 -0
- data/lib/test-factory/foundry.rb +76 -0
- data/lib/test-factory/gem_ext.rb +224 -0
- data/lib/test-factory/page_factory.rb +238 -0
- data/lib/test-factory/string_factory.rb +167 -0
- data/lib/test-factory/test_factory.rb +16 -0
- data/test-factory.gemspec +13 -0
- metadata +78 -0
@@ -0,0 +1,158 @@
|
|
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
|
+
# Some date and time helper functions....
|
16
|
+
module DateFactory
|
17
|
+
|
18
|
+
MONTHS = %w{JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC}
|
19
|
+
|
20
|
+
# Takes a time object and returns a hash containing
|
21
|
+
# various parts of the relevant date.
|
22
|
+
# @param time_object [Time] the moment you want to convert
|
23
|
+
# @returns [Hash] a hash object containing various parts of the date/time you passed to the method
|
24
|
+
#
|
25
|
+
def date_factory(time_object)
|
26
|
+
{
|
27
|
+
sakai: make_date(time_object),
|
28
|
+
sakai_rounded: make_date(time_object).gsub!(/:\d+/, ":#{Time.at(time_object.to_i/(5*60)*(5*60)).strftime("%M")}"), # Date with time rounded to nearest 5-minute mark.
|
29
|
+
short_date: time_object.strftime("%b %-d, %Y"), # => "Oct 18, 2013"
|
30
|
+
samigo: time_object.strftime("%m/%d/%Y %I:%M:%S %p"), # => "10/30/2012 07:02:05 AM"
|
31
|
+
MON: time_object.strftime("%^b"), # => "DEC"
|
32
|
+
Mon: time_object.strftime("%b"), # => "Jan"
|
33
|
+
Month: time_object.strftime("%B"), # => "February"
|
34
|
+
month_int: time_object.month, # => 3
|
35
|
+
day_of_month: time_object.day, # => 17 Note this is not zero-padded
|
36
|
+
weekday: time_object.strftime("%A"), # => "Monday"
|
37
|
+
wkdy: time_object.strftime("%a"), # => "Tue"
|
38
|
+
year: time_object.year, # => 2013
|
39
|
+
hour: time_object.strftime("%I").to_i, # => "07" Zero-padded, 12-hour clock
|
40
|
+
minute: time_object.strftime("%M"), # => "02" Zero-padded
|
41
|
+
minute_rounded: (Time.at(time_object.to_i/(5*60)*(5*60))).strftime("%M"), # => "05" Zero-padded, rounded to 5-minute increments
|
42
|
+
meridian: time_object.strftime("%P"), # => "pm"
|
43
|
+
MERIDIAN: time_object.strftime("%p"), # => "AM"
|
44
|
+
date_w_slashes: time_object.strftime("%m/%d/%Y"), # => 02/08/2013
|
45
|
+
custom: time_object # => Allows creation of a custom date string using the passed time value.
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def an_hour_ago
|
50
|
+
date_factory(Time.now - 3600)
|
51
|
+
end
|
52
|
+
alias last_hour an_hour_ago
|
53
|
+
|
54
|
+
def right_now
|
55
|
+
date_factory(Time.now)
|
56
|
+
end
|
57
|
+
|
58
|
+
def in_an_hour
|
59
|
+
date_factory(Time.now + 3600)
|
60
|
+
end
|
61
|
+
alias next_hour in_an_hour
|
62
|
+
|
63
|
+
def last_year
|
64
|
+
date_factory(Time.now - (3600*24*365))
|
65
|
+
end
|
66
|
+
alias a_year_ago last_year
|
67
|
+
|
68
|
+
# Returns a randomly selected date/time from
|
69
|
+
# within the last year.
|
70
|
+
def in_the_last_year
|
71
|
+
date_factory(Time.random(:year_range=>1))
|
72
|
+
end
|
73
|
+
|
74
|
+
def last_month
|
75
|
+
index = MONTHS.index(current_month)
|
76
|
+
return MONTHS[index-1]
|
77
|
+
end
|
78
|
+
|
79
|
+
def hours_ago(hours)
|
80
|
+
date_factory(Time.now - hours*3600)
|
81
|
+
end
|
82
|
+
|
83
|
+
def hours_from_now(hours)
|
84
|
+
date_factory(Time.now + hours*3600)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Takes an integer representing
|
88
|
+
# the count of minutes as the parameter, and
|
89
|
+
# returns the date_factory hash for the
|
90
|
+
# resulting Time value.
|
91
|
+
#
|
92
|
+
def minutes_ago(mins)
|
93
|
+
date_factory(Time.now - mins*60)
|
94
|
+
end
|
95
|
+
|
96
|
+
def minutes_from_now(mins)
|
97
|
+
date_factory(Time.now + mins*60)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the current month as an
|
101
|
+
# upper-case 3-letter string.
|
102
|
+
# example: "JUL"
|
103
|
+
#
|
104
|
+
def current_month
|
105
|
+
Time.now.strftime("%^b")
|
106
|
+
end
|
107
|
+
|
108
|
+
def next_month
|
109
|
+
index = MONTHS.index(current_month)
|
110
|
+
if index < 11
|
111
|
+
return MONTHS[index+1]
|
112
|
+
else
|
113
|
+
return MONTHS[0]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def in_a_year
|
118
|
+
date_factory(Time.now + (3600*24*365))
|
119
|
+
end
|
120
|
+
alias next_year in_a_year
|
121
|
+
|
122
|
+
def yesterday
|
123
|
+
date_factory(Time.now - (3600*24))
|
124
|
+
end
|
125
|
+
|
126
|
+
def tomorrow
|
127
|
+
date_factory(Time.now + (3600*24))
|
128
|
+
end
|
129
|
+
|
130
|
+
def in_a_week
|
131
|
+
date_factory(Time.now + (3600*24*7))
|
132
|
+
end
|
133
|
+
alias next_week in_a_week
|
134
|
+
|
135
|
+
def a_week_ago
|
136
|
+
date_factory(Time.now - (3600*24*7))
|
137
|
+
end
|
138
|
+
|
139
|
+
def next_monday
|
140
|
+
date_factory(Time.at(Time.now+(8-Time.now.wday)*24*3600))
|
141
|
+
end
|
142
|
+
|
143
|
+
# Formats a date string Sakai-style.
|
144
|
+
# Useful for verifying creation dates and such.
|
145
|
+
#
|
146
|
+
# @param time_object [Time] the moment that you want converted to the string
|
147
|
+
# @returns [String] a date formatted to look like this: Jun 8, 2012 12:02 pm
|
148
|
+
#
|
149
|
+
def make_date(time_object)
|
150
|
+
month = time_object.strftime("%b ")
|
151
|
+
day = time_object.strftime("%-d")
|
152
|
+
year = time_object.strftime(", %Y ")
|
153
|
+
mins = time_object.strftime(":%M %P")
|
154
|
+
hour = time_object.strftime("%l").to_i
|
155
|
+
return month + day + year + hour.to_s + mins
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
@@ -0,0 +1,76 @@
|
|
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
|
+
# This module provides methods that instantiate the page and data classes.
|
16
|
+
module Foundry
|
17
|
+
|
18
|
+
# Using the page_url defined in the provided page_class,
|
19
|
+
# this method will enter that url into the browser's address bar, then run the block of
|
20
|
+
# code that you specify.
|
21
|
+
# @param page_class [Class] the name of the page class that you want to instantiate
|
22
|
+
# @param &block [C] this is the block of code that you want to run while on the given page
|
23
|
+
#
|
24
|
+
def visit page_class, &block
|
25
|
+
on page_class, true, &block
|
26
|
+
end
|
27
|
+
|
28
|
+
# Instantiates the supplied page class, then runs the supplied block of code. Use this
|
29
|
+
# method when you are already on the site page you want to interact with.
|
30
|
+
# @param page_class [Class] the name of the page class that you want to instantiate
|
31
|
+
# @param visit [TrueClass, FalseClass] Essentially you will never have to specify this explicitly
|
32
|
+
# @param &block [C] this is the block of code that you want to run while on the given page
|
33
|
+
#
|
34
|
+
def on page_class, visit=false, &block
|
35
|
+
$current_page = page_class.new @browser, visit
|
36
|
+
block.call $current_page if block
|
37
|
+
$current_page
|
38
|
+
end
|
39
|
+
alias_method :on_page, :on
|
40
|
+
|
41
|
+
# Use this for making a data object in your test steps
|
42
|
+
#
|
43
|
+
# @param data_object_class [Class] The name of the class you want to use to build a data object for testing
|
44
|
+
# @param opts [Hash] The list of attributes you want to give to your data object
|
45
|
+
#
|
46
|
+
def make data_object_class, opts={}
|
47
|
+
data_object_class.new @browser, opts
|
48
|
+
end
|
49
|
+
|
50
|
+
# An extension of the #make method that simplifies and improves
|
51
|
+
# the readability of your "create" step definitions by
|
52
|
+
# combining the make with the create. Of course, this
|
53
|
+
# requires that your data object classes properly follow the design
|
54
|
+
# pattern and have a #create method available.
|
55
|
+
#
|
56
|
+
def create data_object_class, opts={}
|
57
|
+
data_object = make data_object_class, opts
|
58
|
+
data_object.create
|
59
|
+
data_object
|
60
|
+
end
|
61
|
+
|
62
|
+
# A helper method that takes a block of code and waits until it resolves to true.
|
63
|
+
# Useful when you need to wait for something to be on a page that's a little more
|
64
|
+
# involved than a simple element (for those, you should use the #expected_element
|
65
|
+
# method found in the PageFactory class)
|
66
|
+
# @param timeout [Fixnum] Defaults to 30 seconds
|
67
|
+
# @param message [String] The text thrown if the timeout is reached
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# page.wait_until { |b| b.processing_message=="Done" }
|
71
|
+
#
|
72
|
+
def wait_until(timeout=30, message=nil, &block)
|
73
|
+
Object::Watir::Wait.until(timeout, message, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,224 @@
|
|
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
|
+
# We are extending Watir's element methods here with the #fit method,
|
16
|
+
# which can be used with text fields, select lists, radio buttons,
|
17
|
+
# and checkboxes.
|
18
|
+
#
|
19
|
+
# The purpose of +#fit+ is to allow the creation, in your Data Object classes,
|
20
|
+
# of a minimal number of +#edit+ methods (ideally only one) written as
|
21
|
+
# concisely as possible.
|
22
|
+
#
|
23
|
+
# Without the +#fit+ method, you would either have to write separate edit
|
24
|
+
# methods for every possible field you want to edit, or else your
|
25
|
+
# edit method would have to contain lots of repetitive conditional code
|
26
|
+
# to prevent making inadvertent updates to those fields that don't need it.
|
27
|
+
#
|
28
|
+
# Proper use of the +#fit+ method requires following a particular coding
|
29
|
+
# pattern, however:
|
30
|
+
#
|
31
|
+
# * In your Page Classes, define your text field, select list, radio button, and
|
32
|
+
# checkbox elements directly. Do not define +#select+, +#set+ and/or +#clear+
|
33
|
+
# actions there.
|
34
|
+
# * Your data object's instance variables for radio buttons and checkboxes, when
|
35
|
+
# not +nil+, should have the values of +:set+ or +:clear+. If they *need* to be
|
36
|
+
# something else, then define a Hash transform method to easily convert the
|
37
|
+
# custom values back to +:set+ or +:clear+, then pass that transform to the +#fit+ method.
|
38
|
+
# * Always remember to end your +#edit+ methods with the +#set_options()+
|
39
|
+
# method (a.k.a. +#update_options+), from the DataFactory module. It
|
40
|
+
# automatically takes care of updating your data object's instance variables
|
41
|
+
# with any new values.
|
42
|
+
#
|
43
|
+
# ==Example
|
44
|
+
#
|
45
|
+
# Let's take a look at how the proper use of +#fit+ in your code can significantly
|
46
|
+
# clean things up, using a checkbox field for our example. Remember that +#fit+
|
47
|
+
# works with radio buttons, text fields, and select lists, too.
|
48
|
+
#
|
49
|
+
# First, here's some code written without using +#fit+, and using
|
50
|
+
# actions for the checkbox page objects, and a Data Object
|
51
|
+
# instance variable, +@option+, that is either "YES" or "NO"...
|
52
|
+
#
|
53
|
+
# class MyPage < BasePage
|
54
|
+
# # ...
|
55
|
+
# action(:check_checkbox) { |b| b.checkbox(id: "checkbox").set }
|
56
|
+
# action(:clear_checkbox) { |b| b.checkbox(id: "checkbox").clear }
|
57
|
+
# # ...
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# class DataObject
|
61
|
+
# # ...
|
62
|
+
# def edit opts={}
|
63
|
+
# # ...
|
64
|
+
# if opts[:option] != @option
|
65
|
+
# on MyPage do |page|
|
66
|
+
# if opts[:option] == "NO"
|
67
|
+
# page.clear_checkbox
|
68
|
+
# else
|
69
|
+
# page.check_checkbox
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
# @option = opts[:option]
|
73
|
+
# end
|
74
|
+
# # ...
|
75
|
+
# end
|
76
|
+
# # ...
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# That's just nasty! Your Page Class has two element definitions that are nearly identical.
|
80
|
+
# And the nested conditional in the Data Object's #edit method hurts the eyes!
|
81
|
+
#
|
82
|
+
# Now, let's take that same code, but this time use the +#fit+ method. We'll assume that
|
83
|
+
# the data object's +@option+ instance variable will be +:set+, +:clear+, or +nil+, and
|
84
|
+
# end the +#edit+ with the DataFactory's +#set_options+ helper method...
|
85
|
+
#
|
86
|
+
# class MyPage < BasePage
|
87
|
+
# # ...
|
88
|
+
# element(:checkbox) { |b| b.checkbox(id: "checkbox") }
|
89
|
+
# # ...
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# class DataObject
|
93
|
+
# # ...
|
94
|
+
# def edit opts={}
|
95
|
+
# # ...
|
96
|
+
# on MyPage do |page|
|
97
|
+
# # ...
|
98
|
+
# page.checkbox.fit opts[:option]
|
99
|
+
# # ...
|
100
|
+
# end
|
101
|
+
# # ...
|
102
|
+
# update_options opts
|
103
|
+
# end
|
104
|
+
# # ...
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# Much cleaner!
|
108
|
+
#
|
109
|
+
# The +#fit+ method is designed to allow for other values than just +:set+ or +:clear+. It will support
|
110
|
+
# 'Yes', 'No', 'on', 'off' (and it's case insensitive), +true+, and +false+, as well. This way you don't have to worry so much
|
111
|
+
# about making methods that transform from one type to another.
|
112
|
+
#
|
113
|
+
module Watir
|
114
|
+
|
115
|
+
module Container
|
116
|
+
def frm
|
117
|
+
case
|
118
|
+
when div(id: 'embedded').exists?
|
119
|
+
iframe(id: /easyXDM_default\d+_provider/).iframe(id: 'iframeportlet')
|
120
|
+
when div(id: 'Uif-ViewContentWrapper').exists?
|
121
|
+
iframe(class: 'uif-iFrame uif-boxLayoutVerticalItem pull-left clearfix')
|
122
|
+
else
|
123
|
+
self
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class CheckBox
|
129
|
+
def fit(arg)
|
130
|
+
setting = TestFactory.binary_transform(arg)
|
131
|
+
self.send(setting) unless setting==nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Radio
|
136
|
+
def fit(arg)
|
137
|
+
setting = TestFactory.binary_transform(arg)
|
138
|
+
self.set if setting==:set
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
module UserEditable
|
143
|
+
|
144
|
+
# Extends Watir's methods.
|
145
|
+
# Use when the argument you are passing to a text field
|
146
|
+
# may be nil, in which case you don't
|
147
|
+
# want to do anything with the page element.
|
148
|
+
#
|
149
|
+
def fit(args)
|
150
|
+
unless args==nil
|
151
|
+
assert_exists
|
152
|
+
assert_writable
|
153
|
+
|
154
|
+
@element.clear
|
155
|
+
@element.send_keys(args)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
class Select
|
161
|
+
|
162
|
+
# Extends Watir's methods.
|
163
|
+
# Use when the argument you are passing to a text field
|
164
|
+
# may be nil, in which case you don't
|
165
|
+
# want to do anything with the page element.
|
166
|
+
# @example
|
167
|
+
# page.select_list.fit @my_selection
|
168
|
+
#
|
169
|
+
def fit(str_or_rx)
|
170
|
+
select_by :text, str_or_rx unless str_or_rx==nil
|
171
|
+
end
|
172
|
+
|
173
|
+
# Allows you to select a specific item in a
|
174
|
+
# select list, or, if desired, it will pick an item from
|
175
|
+
# the list at random.
|
176
|
+
#
|
177
|
+
# If you pass this method the string '::random::' then
|
178
|
+
# it will select an item at random from the select
|
179
|
+
# list and, assuming what you passed it was a class instance
|
180
|
+
# variable, it will be updated to contain the
|
181
|
+
# selected value (hence the ! in the method name).
|
182
|
+
# Note that this method will be slow with large selection lists.
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# @my_selection='::random::'
|
186
|
+
# page.select_list.pick! @my_selection
|
187
|
+
# puts @my_selection # => <Value of randomly selected item from list>
|
188
|
+
#
|
189
|
+
def pick!(item)
|
190
|
+
if item=='::random::'
|
191
|
+
item.replace(select_at_random)
|
192
|
+
else
|
193
|
+
fit item
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Same as #pick!, except it does not change the
|
198
|
+
# value of 'item'
|
199
|
+
#
|
200
|
+
def pick(item)
|
201
|
+
if item=='::random::'
|
202
|
+
select_at_random
|
203
|
+
else
|
204
|
+
fit item
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def select_at_random
|
211
|
+
ar = options.map(&:text)
|
212
|
+
# Must break out of this method if the select list has nothing to select...
|
213
|
+
return '::random::' if ar.size==1 && (ar[0]=~/^select(.?)$/i || ar[0]=='')
|
214
|
+
sel = ar.sample
|
215
|
+
while sel=~/^select(.?)$/i || sel==''
|
216
|
+
sel = ar.sample
|
217
|
+
end
|
218
|
+
select sel
|
219
|
+
sel
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|