iu-test-factory 0.5.4.2
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 +1 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +28 -0
- data/LICENSE +68 -0
- data/README.md +241 -0
- data/iu-test-factory.gemspec +13 -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 +211 -0
- data/lib/test-factory/page_factory.rb +219 -0
- data/lib/test-factory/string_factory.rb +167 -0
- data/lib/test-factory/test_factory.rb +16 -0
- metadata +73 -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: timeout, message: message, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,211 @@
|
|
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
|
+
class CheckBox
|
116
|
+
def fit(arg)
|
117
|
+
setting = TestFactory.binary_transform(arg)
|
118
|
+
self.send(setting) unless setting==nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Radio
|
123
|
+
def fit(arg)
|
124
|
+
setting = TestFactory.binary_transform(arg)
|
125
|
+
self.set if setting==:set
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
module UserEditable
|
130
|
+
|
131
|
+
# Extends Watir's methods.
|
132
|
+
# Use when the argument you are passing to a text field
|
133
|
+
# may be nil, in which case you don't
|
134
|
+
# want to do anything with the page element.
|
135
|
+
#
|
136
|
+
def fit(args)
|
137
|
+
unless args==nil
|
138
|
+
assert_exists
|
139
|
+
assert_writable
|
140
|
+
|
141
|
+
@element.clear
|
142
|
+
@element.send_keys(args)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Select
|
148
|
+
|
149
|
+
# Extends Watir's methods.
|
150
|
+
# Use when the argument you are passing to a text field
|
151
|
+
# may be nil, in which case you don't
|
152
|
+
# want to do anything with the page element.
|
153
|
+
# @example
|
154
|
+
# page.select_list.fit @my_selection
|
155
|
+
#
|
156
|
+
def fit(str_or_rx)
|
157
|
+
select_by :text, str_or_rx unless str_or_rx==nil
|
158
|
+
end
|
159
|
+
|
160
|
+
# Allows you to select a specific item in a
|
161
|
+
# select list, or, if desired, it will pick an item from
|
162
|
+
# the list at random.
|
163
|
+
#
|
164
|
+
# If you pass this method the string '::random::' then
|
165
|
+
# it will select an item at random from the select
|
166
|
+
# list and, assuming what you passed it was a class instance
|
167
|
+
# variable, it will be updated to contain the
|
168
|
+
# selected value (hence the ! in the method name).
|
169
|
+
# Note that this method will be slow with large selection lists.
|
170
|
+
#
|
171
|
+
# @example
|
172
|
+
# @my_selection='::random::'
|
173
|
+
# page.select_list.pick! @my_selection
|
174
|
+
# puts @my_selection # => <Value of randomly selected item from list>
|
175
|
+
#
|
176
|
+
def pick!(item)
|
177
|
+
if item=='::random::'
|
178
|
+
item.replace(select_at_random)
|
179
|
+
else
|
180
|
+
fit item
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Same as #pick!, except it does not change the
|
185
|
+
# value of 'item'
|
186
|
+
#
|
187
|
+
def pick(item)
|
188
|
+
if item=='::random::'
|
189
|
+
select_at_random
|
190
|
+
else
|
191
|
+
fit item
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def select_at_random
|
198
|
+
ar = options.map(&:text)
|
199
|
+
# Must break out of this method if the select list has nothing to select...
|
200
|
+
return '::random::' if ar.size==1 && (ar[0]=~/^select(.?)$/i || ar[0]=='')
|
201
|
+
sel = ar.sample
|
202
|
+
while sel=~/^select(.?)$/i || sel==''
|
203
|
+
sel = ar.sample
|
204
|
+
end
|
205
|
+
select sel
|
206
|
+
sel
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
@@ -0,0 +1,219 @@
|
|
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 PageFactory class provides a set of methods that allow the rapid creation of page element definitions--known
|
16
|
+
# colloquially as "page objects". These elements are defined using Watir syntax. Please see www.watir.com if you are
|
17
|
+
# not familiar with Watir.
|
18
|
+
#
|
19
|
+
class PageFactory
|
20
|
+
|
21
|
+
# As the PageFactory will be the superclass for all your page classes, having this initialize
|
22
|
+
# method here means it's only written once.
|
23
|
+
#
|
24
|
+
def initialize browser, visit = false
|
25
|
+
@browser = browser
|
26
|
+
goto if visit
|
27
|
+
expected_element if respond_to? :expected_element
|
28
|
+
has_expected_title? if respond_to? :has_expected_title?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Catches any "missing" methods and passes them to the browser object--which means
|
32
|
+
# that Watir will take care of parsing them, so the assumption is that the method being
|
33
|
+
# passed is a valid method for the browser object.
|
34
|
+
#
|
35
|
+
def method_missing sym, *args, &block
|
36
|
+
@browser.send sym, *args, &block
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
|
41
|
+
# Define this in a page class and when you use the "visit" method to instantiate the class
|
42
|
+
# it will enter the URL in the browser's address bar.
|
43
|
+
#
|
44
|
+
def page_url url
|
45
|
+
define_method 'goto' do
|
46
|
+
@browser.goto url
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Define this in a page class and when that class is instantiated it will wait until that
|
51
|
+
# element appears on the page before continuing with the script.
|
52
|
+
# @param element_name [Symbol] The method name of the element that must be present on the page
|
53
|
+
#
|
54
|
+
def expected_element element_name, timeout=30
|
55
|
+
define_method 'expected_element' do
|
56
|
+
self.send(element_name).wait_until_present timeout: timeout
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Define this in a page class and when the class is instantiated it will verify that
|
61
|
+
# the browser's title matches the expected title. If there isn't a match, it raises an
|
62
|
+
# error and halts the script.
|
63
|
+
# @param expected_title [String] The exact text that is expected to appear in the Browser title when the page loads
|
64
|
+
#
|
65
|
+
def expected_title expected_title
|
66
|
+
define_method 'has_expected_title?' do
|
67
|
+
has_expected_title = expected_title.kind_of?(Regexp) ? expected_title =~ @browser.title : expected_title == @browser.title
|
68
|
+
raise "Expected title '#{expected_title}' instead of '#{@browser.title}'" unless has_expected_title
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# The basic building block for defining and interacting with
|
73
|
+
# elements on a web page. # Use in conjunction with
|
74
|
+
# Watir to define all elements on a given page that are important to validate.
|
75
|
+
#
|
76
|
+
# Methods that take one or more parameters can be built with this as well.
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# element(:title) { |b| b.text_field(:id=>"title-id") }
|
80
|
+
# value(:page_header) { |b| b.h3(:class=>"page_header").text }
|
81
|
+
# action(:continue) { |b| b.frm.button(:value=>"Continue").click } => Creates a #continue method that clicks the Continue button
|
82
|
+
# p_element(:select_style) { |stylename, b| b.div(:text=>/#{Regexp.escape(stylename)}/).link(:text=>"Select").click } => #select_style(stylename)
|
83
|
+
#
|
84
|
+
def element name, &block
|
85
|
+
raise "#{name} is being defined twice in #{self}!" if self.instance_methods.include?(name.to_sym)
|
86
|
+
define_method name.to_s do |*thing|
|
87
|
+
Proc.new(&block).call *thing, self
|
88
|
+
end
|
89
|
+
end
|
90
|
+
alias_method :action, :element
|
91
|
+
alias_method :value, :element
|
92
|
+
alias_method :p_element, :element
|
93
|
+
alias_method :p_action, :element
|
94
|
+
alias_method :p_value, :element
|
95
|
+
|
96
|
+
# Use this for links that are safe to define by their text string.
|
97
|
+
# This method will return two methods for interacting with the link:
|
98
|
+
# one that refers to the link itself, and one that clicks on it.
|
99
|
+
# Since it's assumed that the most common thing done with a link is to click it,
|
100
|
+
# the method for clicking it will be named according to the text of the link,
|
101
|
+
# and the method for the link itself will have "_link" appended to it. Any special
|
102
|
+
# characters are stripped from the string. Capital letters are made lower case.
|
103
|
+
# And spaces and dashes are converted to underscores.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
# link("Click Me For Fun!") => Creates the methods #click_me_for_fun and #click_me_for_fun_link
|
107
|
+
#
|
108
|
+
# The last parameter in the method is optional. Use it when
|
109
|
+
# you need the method name to be different from the text of
|
110
|
+
# the link--for example if the link text is something unhelpful,
|
111
|
+
# like "here", or else the link text gets updated (e.g., what was
|
112
|
+
# "Log In" is now "Sign In", instead) and you don't
|
113
|
+
# want to have to go through all your data objects and step
|
114
|
+
# definitions to update them to the new method name.
|
115
|
+
#
|
116
|
+
# @example
|
117
|
+
# link("Click Me For Fun!", :click_me) => Creates the methods #click_me and #click_me_link
|
118
|
+
#
|
119
|
+
def link link_text, *alias_name
|
120
|
+
elementize(:link, link_text, *alias_name)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Use this for buttons that are safe to define by their value attribute.
|
124
|
+
# This method will return two methods for interacting with the button:
|
125
|
+
# one that refers to the button itself, and one that clicks on it.
|
126
|
+
# Since it's assumed that the most common thing done with a button is to click it,
|
127
|
+
# the method for clicking it will be named according to the value of the button,
|
128
|
+
# and the method for the button itself will have "_button" appended to it. Any special
|
129
|
+
# characters are stripped from the string. Capital letters are made lower case.
|
130
|
+
# And spaces and dashes are converted to underscores.
|
131
|
+
# @param button_text [String] The contents of the button's value tag in the HTML
|
132
|
+
#
|
133
|
+
# @example
|
134
|
+
# button("Click Me For Fun!") => Creates the methods #click_me_for_fun and #click_me_for_fun_button
|
135
|
+
#
|
136
|
+
# The last parameter in the method is optional. Use it when
|
137
|
+
# you need the method name to be different from the text of
|
138
|
+
# the button--for example if the button text is unhelpful, like "Go", or else
|
139
|
+
# it changes (e.g., from "Update" to "Edit") and you don't
|
140
|
+
# want to have to go through all your data objects and step
|
141
|
+
# definitions to update them to the new method name.
|
142
|
+
#
|
143
|
+
# @example
|
144
|
+
# link("Click Me For Fun!", :click_me) => Creates the methods #click_me and #click_me_link
|
145
|
+
#
|
146
|
+
def button button_text, *alias_name
|
147
|
+
elementize(:button, button_text, *alias_name)
|
148
|
+
end
|
149
|
+
|
150
|
+
# TestFactory doesn't allow defining a method in a child class
|
151
|
+
# with the same name as one already defined in a parent class.
|
152
|
+
# The thinking here is: "Out of sight, out of mind." Meaning:
|
153
|
+
# you or a team mate might not know or have forgotten that a given
|
154
|
+
# element is already defined in a parent class, and so define it
|
155
|
+
# again. TestFactory's restriction is there to help prevent this.
|
156
|
+
#
|
157
|
+
# However, in some cases you may have a child page class with a
|
158
|
+
# special circumstance, where the parent class's version of the
|
159
|
+
# method really doesn't apply, and you want to use the same method
|
160
|
+
# name in this child class because, really, no other method name
|
161
|
+
# would fit quite as well.
|
162
|
+
#
|
163
|
+
# The #undefine method is for those rare cases. Note: If you start
|
164
|
+
# using this method a lot then you should consider that a sign
|
165
|
+
# that perhaps you're putting too many method definitions into
|
166
|
+
# parent page classes.
|
167
|
+
#
|
168
|
+
# @example
|
169
|
+
# undefine :status, :doc_id => Undefines the specified methods in the current class
|
170
|
+
#
|
171
|
+
def undefine *methods
|
172
|
+
methods.each{ |m| undef_method m }
|
173
|
+
end
|
174
|
+
|
175
|
+
def inherited klass
|
176
|
+
klass.instance_eval {
|
177
|
+
|
178
|
+
# Creates a method, #wait_for_ajax, usable in your Page Classes, that executes
|
179
|
+
# the 'jQuery.active' Javascript snippet each second until timeout.
|
180
|
+
#
|
181
|
+
# If timeout is exceeded, raises Watir::Wait::TimeoutError exception. The returned error
|
182
|
+
# message is customizable.
|
183
|
+
#
|
184
|
+
define_method 'wait_for_ajax' do |timeout=10, message|
|
185
|
+
timeout.times do
|
186
|
+
sleep 0.3
|
187
|
+
return true if @browser.execute_script('return jQuery.active').to_i == 0
|
188
|
+
sleep 0.7
|
189
|
+
end
|
190
|
+
raise Watir::Wait::TimeoutError, "Ajax calls continued beyond #{timeout} seconds. #{message}"
|
191
|
+
end
|
192
|
+
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
# A helper method that converts the passed string into snake case. See the StringFactory
|
198
|
+
# module for more info.
|
199
|
+
#
|
200
|
+
def damballa text
|
201
|
+
StringFactory.damballa(text)
|
202
|
+
end
|
203
|
+
|
204
|
+
def elementize type, text, *alias_name
|
205
|
+
hash={:link=>:text, :button=>:value}
|
206
|
+
if alias_name.empty?
|
207
|
+
el_name=damballa("#{text}_#{type}")
|
208
|
+
act_name=damballa(text)
|
209
|
+
else
|
210
|
+
el_name="#{alias_name[0]}_#{type}".to_sym
|
211
|
+
act_name=alias_name[0]
|
212
|
+
end
|
213
|
+
element(el_name) { |b| b.send(type, hash[type]=>text) }
|
214
|
+
action(act_name) { |b| b.send(type, hash[type]=>text).click }
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end # PageFactory
|