iu-test-factory 0.5.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|