testcentricity 2.3.13
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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +93 -0
- data/LICENSE.txt +28 -0
- data/README.md +1634 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/devices/devices.yml +344 -0
- data/lib/testcentricity.rb +144 -0
- data/lib/testcentricity/app_core/appium_connect_helper.rb +154 -0
- data/lib/testcentricity/app_core/appium_server.rb +69 -0
- data/lib/testcentricity/app_core/screen_objects_helper.rb +180 -0
- data/lib/testcentricity/app_core/screen_sections_helper.rb +332 -0
- data/lib/testcentricity/app_elements/app_element_helper.rb +293 -0
- data/lib/testcentricity/app_elements/button.rb +8 -0
- data/lib/testcentricity/app_elements/checkbox.rb +20 -0
- data/lib/testcentricity/app_elements/label.rb +8 -0
- data/lib/testcentricity/app_elements/list.rb +25 -0
- data/lib/testcentricity/app_elements/switch.rb +20 -0
- data/lib/testcentricity/app_elements/textfield.rb +12 -0
- data/lib/testcentricity/browser_helper.rb +174 -0
- data/lib/testcentricity/data_objects/data_objects_helper.rb +78 -0
- data/lib/testcentricity/data_objects/environment.rb +281 -0
- data/lib/testcentricity/data_objects/excel_helper.rb +242 -0
- data/lib/testcentricity/exception_queue_helper.rb +51 -0
- data/lib/testcentricity/utility_helpers.rb +28 -0
- data/lib/testcentricity/version.rb +3 -0
- data/lib/testcentricity/web_core/drag_drop_helper.rb +15 -0
- data/lib/testcentricity/web_core/page_objects_helper.rb +669 -0
- data/lib/testcentricity/web_core/page_sections_helper.rb +866 -0
- data/lib/testcentricity/web_core/webdriver_helper.rb +579 -0
- data/lib/testcentricity/web_elements/button.rb +8 -0
- data/lib/testcentricity/web_elements/cell_button.rb +8 -0
- data/lib/testcentricity/web_elements/cell_checkbox.rb +38 -0
- data/lib/testcentricity/web_elements/cell_element.rb +69 -0
- data/lib/testcentricity/web_elements/cell_image.rb +8 -0
- data/lib/testcentricity/web_elements/cell_radio.rb +31 -0
- data/lib/testcentricity/web_elements/checkbox.rb +100 -0
- data/lib/testcentricity/web_elements/file_field.rb +45 -0
- data/lib/testcentricity/web_elements/image.rb +34 -0
- data/lib/testcentricity/web_elements/label.rb +8 -0
- data/lib/testcentricity/web_elements/link.rb +8 -0
- data/lib/testcentricity/web_elements/list.rb +73 -0
- data/lib/testcentricity/web_elements/list_button.rb +8 -0
- data/lib/testcentricity/web_elements/list_checkbox.rb +38 -0
- data/lib/testcentricity/web_elements/list_element.rb +61 -0
- data/lib/testcentricity/web_elements/list_radio.rb +31 -0
- data/lib/testcentricity/web_elements/radio.rb +74 -0
- data/lib/testcentricity/web_elements/select_list.rb +197 -0
- data/lib/testcentricity/web_elements/siebel_open_ui_helper.rb +15 -0
- data/lib/testcentricity/web_elements/table.rb +612 -0
- data/lib/testcentricity/web_elements/textfield.rb +114 -0
- data/lib/testcentricity/web_elements/ui_elements_helper.rb +502 -0
- data/lib/testcentricity/world_extensions.rb +26 -0
- data/my_templates/default/method_details/setup.rb +3 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/testcentricity_spec.rb +9 -0
- data/testcentricity.gemspec +47 -0
- metadata +328 -0
@@ -0,0 +1,866 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
module TestCentricity
|
4
|
+
class PageSection
|
5
|
+
include Capybara::DSL
|
6
|
+
include Capybara::Node::Matchers
|
7
|
+
include Test::Unit::Assertions
|
8
|
+
|
9
|
+
attr_reader :context, :name
|
10
|
+
attr_accessor :locator
|
11
|
+
attr_accessor :parent
|
12
|
+
attr_accessor :parent_list
|
13
|
+
attr_accessor :list_index
|
14
|
+
attr_accessor :locator_type
|
15
|
+
|
16
|
+
XPATH_SELECTORS = ['//', '[@', '[contains(@']
|
17
|
+
CSS_SELECTORS = ['#', ':nth-child(', ':nth-of-type(', '^=', '$=', '*=']
|
18
|
+
|
19
|
+
def initialize(name, parent, locator, context)
|
20
|
+
@name = name
|
21
|
+
@parent = parent
|
22
|
+
@locator = locator
|
23
|
+
@context = context
|
24
|
+
@parent_list = nil
|
25
|
+
@list_index = nil
|
26
|
+
|
27
|
+
is_xpath = XPATH_SELECTORS.any? { |selector| @locator.include?(selector) }
|
28
|
+
is_css = CSS_SELECTORS.any? { |selector| @locator.include?(selector) }
|
29
|
+
if is_xpath && !is_css
|
30
|
+
@locator_type = :xpath
|
31
|
+
elsif is_css && !is_xpath
|
32
|
+
@locator_type = :css
|
33
|
+
elsif !is_css && !is_xpath
|
34
|
+
@locator_type = :css
|
35
|
+
else
|
36
|
+
raise "Cannot determine type of locator for PageSection '#{@name}' - locator = #{@locator}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_locator
|
41
|
+
if @locator.empty? && defined?(section_locator)
|
42
|
+
locator = section_locator
|
43
|
+
else
|
44
|
+
locator = @locator
|
45
|
+
end
|
46
|
+
|
47
|
+
unless @parent_list.nil?
|
48
|
+
locator = "#{@parent_list.get_locator}|#{locator}"
|
49
|
+
unless @list_index.nil?
|
50
|
+
case @locator_type
|
51
|
+
when :xpath
|
52
|
+
locator = "(#{locator})[#{@list_index}]"
|
53
|
+
when :css
|
54
|
+
locator = "#{locator}:nth-of-type(#{@list_index})"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if @context == :section && !@parent.nil? && !@parent.get_locator.nil?
|
60
|
+
"#{@parent.get_locator}|#{locator}"
|
61
|
+
else
|
62
|
+
locator
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_locator_type
|
67
|
+
@locator_type
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_list_index(list, index = 1)
|
71
|
+
@parent_list = list unless list.nil?
|
72
|
+
@list_index = index
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_item_count
|
76
|
+
raise 'No parent list defined' if @parent_list.nil?
|
77
|
+
@parent_list.get_item_count
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_list_items
|
81
|
+
items = []
|
82
|
+
(1..get_item_count).each do |item|
|
83
|
+
set_list_index(nil, item)
|
84
|
+
items.push(get_value)
|
85
|
+
end
|
86
|
+
items
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_all_items_count
|
90
|
+
raise 'No parent list defined' if @parent_list.nil?
|
91
|
+
@parent_list.get_all_items_count
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_all_list_items
|
95
|
+
items = []
|
96
|
+
(1..get_all_items_count).each do |item|
|
97
|
+
set_list_index(nil, item)
|
98
|
+
items.push(get_value(:all))
|
99
|
+
end
|
100
|
+
items
|
101
|
+
end
|
102
|
+
|
103
|
+
def verify_list_items(expected, enqueue = false)
|
104
|
+
actual = get_list_items
|
105
|
+
enqueue ?
|
106
|
+
ExceptionQueue.enqueue_assert_equal(expected, actual, "Expected list '#{get_name}' (#{get_locator})") :
|
107
|
+
assert_equal(expected, actual, "Expected list object '#{get_name}' (#{get_locator}) to be #{expected} but found #{actual}")
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_object_type
|
111
|
+
:section
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_name
|
115
|
+
@name
|
116
|
+
end
|
117
|
+
|
118
|
+
def set_parent(parent)
|
119
|
+
@parent = parent
|
120
|
+
end
|
121
|
+
|
122
|
+
# Define a trait for this page section.
|
123
|
+
#
|
124
|
+
# @param trait_name [Symbol] name of trait (as a symbol)
|
125
|
+
# @param block [&block] trait value
|
126
|
+
# @example
|
127
|
+
# trait(:section_locator) { "//div[@class='Messaging_Applet']" }
|
128
|
+
# trait(:list_table_name) { '#Messages' }
|
129
|
+
#
|
130
|
+
def self.trait(trait_name, &block)
|
131
|
+
define_method(trait_name.to_s, &block)
|
132
|
+
end
|
133
|
+
|
134
|
+
# Declare and instantiate a single generic UI Element for this page section.
|
135
|
+
#
|
136
|
+
# @param element_name [Symbol] name of UI object (as a symbol)
|
137
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
138
|
+
# @example
|
139
|
+
# element :undo_record_item, "//li[@rn='Undo Record']/a"
|
140
|
+
# element :basket_header, 'div.basket_header'
|
141
|
+
#
|
142
|
+
def self.element(element_name, locator)
|
143
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::UIElement.new("#{element_name}", self, "#{locator}", :section);end))
|
144
|
+
end
|
145
|
+
|
146
|
+
# Declare and instantiate a collection of generic UI Elements for this page section.
|
147
|
+
#
|
148
|
+
# @param element_hash [Hash] names of UI objects (as a symbol) and CSS selectors or XPath expressions that uniquely identifies objects
|
149
|
+
# @example
|
150
|
+
# elements profile_item: 'a#profile',
|
151
|
+
# settings_item: 'a#userPreferencesTrigger',
|
152
|
+
# log_out_item: 'a#logout'
|
153
|
+
#
|
154
|
+
def self.elements(element_hash)
|
155
|
+
element_hash.each do |element_name, locator|
|
156
|
+
element(element_name, locator)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Declare and instantiate a single button UI Element for this page section.
|
161
|
+
#
|
162
|
+
# @param element_name [Symbol] name of button object (as a symbol)
|
163
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
164
|
+
# @example
|
165
|
+
# button :checkout_button, 'button.checkout_button'
|
166
|
+
# button :login_button, "//input[@id='submit_button']"
|
167
|
+
#
|
168
|
+
def self.button(element_name, locator)
|
169
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::Button.new("#{element_name}", self, "#{locator}", :section);end))
|
170
|
+
end
|
171
|
+
|
172
|
+
# Declare and instantiate a collection of buttons for this page section.
|
173
|
+
#
|
174
|
+
# @param element_hash [Hash] names of buttons (as a symbol) and CSS selectors or XPath expressions that uniquely identifies buttons
|
175
|
+
# @example
|
176
|
+
# buttons new_account_button: 'button#new-account',
|
177
|
+
# save_button: 'button#save',
|
178
|
+
# cancel_button: 'button#cancel'
|
179
|
+
#
|
180
|
+
def self.buttons(element_hash)
|
181
|
+
element_hash.each do |element_name, locator|
|
182
|
+
button(element_name, locator)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Declare and instantiate a single text field UI Element for this page section.
|
187
|
+
#
|
188
|
+
# @param element_name [Symbol] name of text field object (as a symbol)
|
189
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
190
|
+
# @example
|
191
|
+
# textfield :user_id_field, "//input[@id='UserName']"
|
192
|
+
# textfield :password_field, 'input#consumer_password'
|
193
|
+
#
|
194
|
+
def self.textfield(element_name, locator)
|
195
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::TextField.new("#{element_name}", self, "#{locator}", :section);end))
|
196
|
+
end
|
197
|
+
|
198
|
+
# Declare and instantiate a collection of text fields for this page section.
|
199
|
+
#
|
200
|
+
# @param element_hash [Hash] names of text fields (as a symbol) and CSS selectors or XPath expressions that uniquely identifies text fields
|
201
|
+
# @example
|
202
|
+
# textfields name_field: 'input#Name',
|
203
|
+
# title_field: 'input#Title',
|
204
|
+
# phone_field: 'input#PhoneNumber',
|
205
|
+
# fax_field: 'input#FaxNumber',
|
206
|
+
# email_field: 'input#Email'
|
207
|
+
#
|
208
|
+
def self.textfields(element_hash)
|
209
|
+
element_hash.each do |element_name, locator|
|
210
|
+
textfield(element_name, locator)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Declare and instantiate a single checkbox UI Element for this page section.
|
215
|
+
#
|
216
|
+
# @param element_name [Symbol] name of checkbox object (as a symbol)
|
217
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
218
|
+
# @param proxy [Symbol] Optional name (as a symbol) of proxy object to receive click actions
|
219
|
+
# @example
|
220
|
+
# checkbox :remember_checkbox, "//input[@id='RememberUser']"
|
221
|
+
# checkbox :accept_terms_checkbox, 'input#accept_terms_conditions', :accept_terms_label
|
222
|
+
#
|
223
|
+
def self.checkbox(element_name, locator, proxy = nil)
|
224
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::CheckBox.new("#{element_name}", self, "#{locator}", :section, #{proxy});end))
|
225
|
+
end
|
226
|
+
|
227
|
+
# Declare and instantiate a collection of checkboxes for this page section.
|
228
|
+
#
|
229
|
+
# @param element_hash [Hash] names of checkboxes (as a symbol) and CSS selectors or XPath expressions that uniquely identifies checkboxes
|
230
|
+
# @example
|
231
|
+
# checkboxes hazmat_certified_check: 'input#hazmatCertified',
|
232
|
+
# epa_certified_check: 'input#epaCertified',
|
233
|
+
# dhs_certified_check: 'input#homelandSecurityCertified',
|
234
|
+
# carb_compliant_check: 'input#carbCompliant'
|
235
|
+
#
|
236
|
+
def self.checkboxes(element_hash)
|
237
|
+
element_hash.each do |element_name, locator|
|
238
|
+
checkbox(element_name, locator)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Declare and instantiate a single radio button UI Element for this page section.
|
243
|
+
#
|
244
|
+
# @param element_name [Symbol] name of radio object (as a symbol)
|
245
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
246
|
+
# @param proxy [Symbol] Optional name (as a symbol) of proxy object to receive click actions
|
247
|
+
# @example
|
248
|
+
# radio :accept_terms_radio, "//input[@id='Accept_Terms']"
|
249
|
+
# radio :decline_terms_radio, 'input#decline_terms_conditions', :decline_terms_label
|
250
|
+
#
|
251
|
+
def self.radio(element_name, locator, proxy = nil)
|
252
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::Radio.new("#{element_name}", self, "#{locator}", :section, #{proxy});end))
|
253
|
+
end
|
254
|
+
|
255
|
+
# Declare and instantiate a collection of radio buttons for this page section.
|
256
|
+
#
|
257
|
+
# @param element_hash [Hash] names of radio buttons (as a symbol) and CSS selectors or XPath expressions that uniquely identifies radio buttons
|
258
|
+
# @example
|
259
|
+
# radios visa_radio: 'input#payWithVisa',
|
260
|
+
# mastercard_radio: 'input#payWithMastercard',
|
261
|
+
# discover_radio: 'input#payWithDiscover',
|
262
|
+
# amex_radio: 'input#payWithAmEx'
|
263
|
+
#
|
264
|
+
def self.radios(element_hash)
|
265
|
+
element_hash.each do |element_name, locator|
|
266
|
+
radio(element_name, locator)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Declare and instantiate a single label UI Element for this page section.
|
271
|
+
#
|
272
|
+
# @param element_name [Symbol] name of label object (as a symbol)
|
273
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
274
|
+
# @example
|
275
|
+
# label :welcome_label, 'div.Welcome'
|
276
|
+
# label :rollup_price_label, "//div[contains(@id, 'Rollup Item Price')]"
|
277
|
+
#
|
278
|
+
def self.label(element_name, locator)
|
279
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::Label.new("#{element_name}", self, "#{locator}", :section);end))
|
280
|
+
end
|
281
|
+
|
282
|
+
def self.labels(element_hash)
|
283
|
+
element_hash.each do |element_name, locator|
|
284
|
+
label(element_name, locator)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Declare and instantiate a single link UI Element for this page section.
|
289
|
+
#
|
290
|
+
# @param element_name [Symbol] name of link object (as a symbol)
|
291
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
292
|
+
# @example
|
293
|
+
# link :registration_link, 'a.account-nav__link.register'
|
294
|
+
# link :shopping_basket_link, "//a[@href='shopping_basket']"
|
295
|
+
#
|
296
|
+
def self.link(element_name, locator)
|
297
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::Link.new("#{element_name}", self, "#{locator}", :section);end))
|
298
|
+
end
|
299
|
+
|
300
|
+
def self.links(element_hash)
|
301
|
+
element_hash.each do |element_name, locator|
|
302
|
+
link(element_name, locator)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Declare and instantiate a single table UI Element for this page section.
|
307
|
+
#
|
308
|
+
# @param element_name [Symbol] name of table object (as a symbol)
|
309
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
310
|
+
# @example
|
311
|
+
# table :payments_table, "//table[@class='payments_table']"
|
312
|
+
#
|
313
|
+
def self.table(element_name, locator)
|
314
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::Table.new("#{element_name}", self, "#{locator}", :section);end))
|
315
|
+
end
|
316
|
+
|
317
|
+
def self.tables(element_hash)
|
318
|
+
element_hash.each do |element_name, locator|
|
319
|
+
table(element_name, locator)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Declare and instantiate a single select list UI Element for this page section.
|
324
|
+
#
|
325
|
+
# @param element_name [Symbol] name of select list object (as a symbol)
|
326
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
327
|
+
# @example
|
328
|
+
# selectlist :category_selector, 'select#search_form_category_chosen'
|
329
|
+
# selectlist :gender_select, "//select[@id='customer_gender']"
|
330
|
+
#
|
331
|
+
def self.selectlist(element_name, locator)
|
332
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::SelectList.new("#{element_name}", self, "#{locator}", :section);end))
|
333
|
+
end
|
334
|
+
|
335
|
+
def self.selectlists(element_hash)
|
336
|
+
element_hash.each do |element_name, locator|
|
337
|
+
selectlist(element_name, locator)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
# Declare and instantiate a single list UI Element for this page section.
|
342
|
+
#
|
343
|
+
# @param element_name [Symbol] name of list object (as a symbol)
|
344
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
345
|
+
# @example
|
346
|
+
# list :y_axis_list, 'g.y_axis'
|
347
|
+
#
|
348
|
+
def self.list(element_name, locator)
|
349
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::List.new("#{element_name}", self, "#{locator}", :section);end))
|
350
|
+
end
|
351
|
+
|
352
|
+
def self.lists(element_hash)
|
353
|
+
element_hash.each do |element_name, locator|
|
354
|
+
list(element_name, locator)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Declare and instantiate an single image UI Element for this page section.
|
359
|
+
#
|
360
|
+
# @param element_name [Symbol] name of image object (as a symbol)
|
361
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
362
|
+
# @example
|
363
|
+
# image :basket_item_image, 'div.product_image'
|
364
|
+
# image :corporate_logo_image, "//img[@alt='MyCompany_logo']"
|
365
|
+
#
|
366
|
+
def self.image(element_name, locator)
|
367
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::Image.new("#{element_name}", self, "#{locator}", :section);end))
|
368
|
+
end
|
369
|
+
|
370
|
+
def self.images(element_hash)
|
371
|
+
element_hash.each do |element_name, locator|
|
372
|
+
image(element_name, locator)
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Declare and instantiate a single File Field UI Element for this page section.
|
377
|
+
#
|
378
|
+
# @param element_name [Symbol] name of file field object (as a symbol)
|
379
|
+
# @param locator [String] CSS selector or XPath expression that uniquely identifies object
|
380
|
+
# @example
|
381
|
+
# filefield :attach_file, 's_SweFileName'
|
382
|
+
#
|
383
|
+
def self.filefield(element_name, locator)
|
384
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::FileField.new("#{element_name}", self, "#{locator}", :section);end))
|
385
|
+
end
|
386
|
+
|
387
|
+
def self.filefields(element_hash)
|
388
|
+
element_hash.each do |element_name, locator|
|
389
|
+
filefield(element_name, locator)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Declare and instantiate a cell button in a table column on this page section.
|
394
|
+
#
|
395
|
+
# @param element_name [Symbol] name of cell button object (as a symbol)
|
396
|
+
# @param locator [String] XPath expression that uniquely identifies cell button within row and column of parent table object
|
397
|
+
# @param table [Symbol] Name (as a symbol) of parent table object
|
398
|
+
# @param column [Integer] 1-based index of table column that contains the cell button object
|
399
|
+
# @example
|
400
|
+
# cell_button :show_button, "a[@class='show']", :data_table, 5
|
401
|
+
# cell_button :edit_button, "a[@class='edit']", :data_table, 5
|
402
|
+
#
|
403
|
+
def self.cell_button(element_name, locator, table, column)
|
404
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::CellButton.new("#{element_name}", self, "#{locator}", :section, #{table}, #{column});end))
|
405
|
+
end
|
406
|
+
|
407
|
+
# Declare and instantiate a cell checkbox in a table column on this page section.
|
408
|
+
#
|
409
|
+
# @param element_name [Symbol] name of cell checkbox object (as a symbol)
|
410
|
+
# @param locator [String] XPath expression that uniquely identifies cell checkbox within row and column of parent table object
|
411
|
+
# @param table [Symbol] Name (as a symbol) of parent table object
|
412
|
+
# @param column [Integer] 1-based index of table column that contains the cell checkbox object
|
413
|
+
# @example
|
414
|
+
# cell_checkbox :is_registered_check, "a[@class='registered']", :data_table, 4
|
415
|
+
#
|
416
|
+
def self.cell_checkbox(element_name, locator, table, column, proxy = nil)
|
417
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::CellCheckBox.new("#{element_name}", self, "#{locator}", :section, #{table}, #{column}, #{proxy});end))
|
418
|
+
end
|
419
|
+
|
420
|
+
# Declare and instantiate a cell radio in a table column on this page section.
|
421
|
+
#
|
422
|
+
# @param element_name [Symbol] name of cell radio object (as a symbol)
|
423
|
+
# @param locator [String] XPath expression that uniquely identifies cell radio within row and column of parent table object
|
424
|
+
# @param table [Symbol] Name (as a symbol) of parent table object
|
425
|
+
# @param column [Integer] 1-based index of table column that contains the cell radio object
|
426
|
+
# @example
|
427
|
+
# cell_radio :track_a_radio, "a[@class='track_a']", :data_table, 8
|
428
|
+
#
|
429
|
+
def self.cell_radio(element_name, locator, table, column, proxy = nil)
|
430
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::CellRadio.new("#{element_name}", self, "#{locator}", :section, #{table}, #{column}, #{proxy});end))
|
431
|
+
end
|
432
|
+
|
433
|
+
# Declare and instantiate a cell image in a table column on this page object.
|
434
|
+
#
|
435
|
+
# @param element_name [Symbol] name of cell image object (as a symbol)
|
436
|
+
# @param locator [String] XPath expression that uniquely identifies cell image within row and column of parent table object
|
437
|
+
# @param table [Symbol] Name (as a symbol) of parent table object
|
438
|
+
# @param column [Integer] 1-based index of table column that contains the cell image object
|
439
|
+
# @example
|
440
|
+
# cell_image :ready_icon, "img[@class='ready']", :data_table, 3
|
441
|
+
# cell_image :send_icon, "img[@class='send']", :data_table, 3
|
442
|
+
#
|
443
|
+
def self.cell_image(element_name, locator, table, column)
|
444
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::CellImage.new("#{element_name}", self, "#{locator}", :section, #{table}, #{column});end))
|
445
|
+
end
|
446
|
+
|
447
|
+
# Declare and instantiate a list button in a row of a list object on this section object.
|
448
|
+
#
|
449
|
+
# @param element_name [Symbol] name of list button object (as a symbol)
|
450
|
+
# @param locator [String] XPath expression that uniquely identifies list button within row of parent list object
|
451
|
+
# @param list [Symbol] Name (as a symbol) of parent list object
|
452
|
+
# @example
|
453
|
+
# list_button :delete_button, "a[@class='delete']", :icon_list
|
454
|
+
# list_button :edit_button, "a[@class='edit']", :icon_list
|
455
|
+
#
|
456
|
+
def self.list_button(element_name, locator, list)
|
457
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::ListButton.new("#{element_name}", self, "#{locator}", :section, #{list});end))
|
458
|
+
end
|
459
|
+
|
460
|
+
# Declare and instantiate a list checkbox in a row of a list object on this section object.
|
461
|
+
#
|
462
|
+
# @param element_name [Symbol] name of list checkbox object (as a symbol)
|
463
|
+
# @param locator [String] XPath expression that uniquely identifies list checkbox within row of parent list object
|
464
|
+
# @param list [Symbol] Name (as a symbol) of parent list object
|
465
|
+
# @example
|
466
|
+
# list_checkbox :is_registered_check, "a[@class='registered']", :data_list
|
467
|
+
#
|
468
|
+
def self.list_checkbox(element_name, locator, list, proxy = nil)
|
469
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::ListCheckBox.new("#{element_name}", self, "#{locator}", :section, #{list}, #{proxy});end))
|
470
|
+
end
|
471
|
+
|
472
|
+
# Declare and instantiate a list radio in a row of a list object on this section object.
|
473
|
+
#
|
474
|
+
# @param element_name [Symbol] name of list radio object (as a symbol)
|
475
|
+
# @param locator [String] XPath expression that uniquely identifies list radio within row of parent list object
|
476
|
+
# @param list [Symbol] Name (as a symbol) of parent list object
|
477
|
+
# @example
|
478
|
+
# list_radio :sharing_radio, "a[@class='sharing']", :data_list
|
479
|
+
#
|
480
|
+
def self.list_radio(element_name, locator, list, proxy = nil)
|
481
|
+
class_eval(%(def #{element_name};@#{element_name} ||= TestCentricity::CellRadio.new("#{element_name}", self, "#{locator}", :section, #{list}, #{proxy});end))
|
482
|
+
end
|
483
|
+
|
484
|
+
# Instantiate a single PageSection object within this PageSection object.
|
485
|
+
#
|
486
|
+
# @param section_name [Symbol] name of PageSection object (as a symbol)
|
487
|
+
# @param class_name [String] Class name of PageSection object
|
488
|
+
# @example
|
489
|
+
# section :search_form, SearchForm
|
490
|
+
#
|
491
|
+
def self.section(section_name, class_name, locator = nil)
|
492
|
+
class_eval(%(def #{section_name};@#{section_name} ||= #{class_name}.new("#{section_name}", self, "#{locator}", :section);end))
|
493
|
+
end
|
494
|
+
|
495
|
+
def self.sections(section_hash)
|
496
|
+
section_hash.each do |section_name, class_name|
|
497
|
+
section(section_name, class_name)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# Does Section object exists?
|
502
|
+
#
|
503
|
+
# @return [Boolean]
|
504
|
+
# @example
|
505
|
+
# navigation_toolbar.exists?
|
506
|
+
#
|
507
|
+
def exists?
|
508
|
+
section, = find_section
|
509
|
+
section != nil
|
510
|
+
end
|
511
|
+
|
512
|
+
# Is Section object enabled?
|
513
|
+
#
|
514
|
+
# @return [Boolean]
|
515
|
+
# @example
|
516
|
+
# bar_chart_section.enabled?
|
517
|
+
#
|
518
|
+
def enabled?
|
519
|
+
!disabled?
|
520
|
+
end
|
521
|
+
|
522
|
+
# Is Section object disabled (not enabled)?
|
523
|
+
#
|
524
|
+
# @return [Boolean]
|
525
|
+
# @example
|
526
|
+
# bar_chart_section.disabled?
|
527
|
+
#
|
528
|
+
def disabled?
|
529
|
+
section, = find_section
|
530
|
+
raise "Section object '#{get_name}' (#{get_locator}) not found" unless section
|
531
|
+
section.disabled?
|
532
|
+
end
|
533
|
+
|
534
|
+
# Is Section object visible?
|
535
|
+
#
|
536
|
+
# @return [Boolean]
|
537
|
+
# @example
|
538
|
+
# navigation_toolbar.visible?
|
539
|
+
#
|
540
|
+
def visible?
|
541
|
+
section, = find_section
|
542
|
+
exists = section
|
543
|
+
visible = false
|
544
|
+
visible = section.visible? if exists
|
545
|
+
# the section is visible if it exists and it is not invisible
|
546
|
+
exists && visible ? true : false
|
547
|
+
end
|
548
|
+
|
549
|
+
# Is Section object hidden (not visible)?
|
550
|
+
#
|
551
|
+
# @return [Boolean]
|
552
|
+
# @example
|
553
|
+
# navigation_toolbar.hidden?
|
554
|
+
#
|
555
|
+
def hidden?
|
556
|
+
!visible?
|
557
|
+
end
|
558
|
+
|
559
|
+
# Is section object displayed in browser window?
|
560
|
+
#
|
561
|
+
# @return [Boolean]
|
562
|
+
# @example
|
563
|
+
# navigation_toolbar.displayed??
|
564
|
+
#
|
565
|
+
def displayed?
|
566
|
+
section, = find_section
|
567
|
+
raise "Section object '#{get_name}' (#{get_locator}) not found" unless section
|
568
|
+
section.displayed?
|
569
|
+
end
|
570
|
+
|
571
|
+
# Wait until the Section object exists, or until the specified wait time has expired. If the wait time is nil, then
|
572
|
+
# the wait time will be Capybara.default_max_wait_time.
|
573
|
+
#
|
574
|
+
# @param seconds [Integer or Float] wait time in seconds
|
575
|
+
# @example
|
576
|
+
# navigation_toolbar.wait_until_exists(0.5)
|
577
|
+
#
|
578
|
+
def wait_until_exists(seconds = nil)
|
579
|
+
timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
|
580
|
+
wait = Selenium::WebDriver::Wait.new(timeout: timeout)
|
581
|
+
wait.until { exists? }
|
582
|
+
rescue
|
583
|
+
raise "Could not find Section object '#{get_name}' (#{get_locator}) after #{timeout} seconds" unless exists?
|
584
|
+
end
|
585
|
+
|
586
|
+
# Wait until the Section object no longer exists, or until the specified wait time has expired. If the wait time is
|
587
|
+
# nil, then the wait time will be Capybara.default_max_wait_time.
|
588
|
+
#
|
589
|
+
# @param seconds [Integer or Float] wait time in seconds
|
590
|
+
# @example
|
591
|
+
# navigation_toolbar.wait_until_gone(5)
|
592
|
+
#
|
593
|
+
def wait_until_gone(seconds = nil)
|
594
|
+
timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
|
595
|
+
wait = Selenium::WebDriver::Wait.new(timeout: timeout)
|
596
|
+
wait.until { !exists? }
|
597
|
+
rescue
|
598
|
+
raise "Section object '#{get_name}' (#{get_locator}) remained visible after #{timeout} seconds" if exists?
|
599
|
+
end
|
600
|
+
|
601
|
+
# Wait until the Section object is visible, or until the specified wait time has expired. If the wait time is nil,
|
602
|
+
# then the wait time will be Capybara.default_max_wait_time.
|
603
|
+
#
|
604
|
+
# @param seconds [Integer or Float] wait time in seconds
|
605
|
+
# @example
|
606
|
+
# bar_chart_section.wait_until_visible(0.5)
|
607
|
+
#
|
608
|
+
def wait_until_visible(seconds = nil)
|
609
|
+
timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
|
610
|
+
wait = Selenium::WebDriver::Wait.new(timeout: timeout)
|
611
|
+
wait.until { visible? }
|
612
|
+
rescue
|
613
|
+
raise "Could not find Section object '#{get_name}' (#{get_locator}) after #{timeout} seconds" unless visible?
|
614
|
+
end
|
615
|
+
|
616
|
+
# Wait until the Section object is hidden, or until the specified wait time has expired. If the wait time is nil,
|
617
|
+
# then the wait time will be Capybara.default_max_wait_time.
|
618
|
+
#
|
619
|
+
# @param seconds [Integer or Float] wait time in seconds
|
620
|
+
# @example
|
621
|
+
# bar_chart_section.wait_until_hidden(10)
|
622
|
+
#
|
623
|
+
def wait_until_hidden(seconds = nil)
|
624
|
+
timeout = seconds.nil? ? Capybara.default_max_wait_time : seconds
|
625
|
+
wait = Selenium::WebDriver::Wait.new(timeout: timeout)
|
626
|
+
wait.until { hidden? }
|
627
|
+
rescue
|
628
|
+
raise "Section object '#{get_name}' (#{get_locator}) remained visible after #{timeout} seconds" if visible?
|
629
|
+
end
|
630
|
+
|
631
|
+
# Click at a specific location within a Section object
|
632
|
+
#
|
633
|
+
# @param x [Integer] X offset
|
634
|
+
# @param y [Integer] Y offset
|
635
|
+
# @example
|
636
|
+
# bar_chart_section.click_at(10, 10)
|
637
|
+
#
|
638
|
+
def click_at(x, y)
|
639
|
+
section, = find_section
|
640
|
+
raise "Section object '#{get_name}' (#{get_locator}) not found" unless section
|
641
|
+
section.click_at(x, y)
|
642
|
+
end
|
643
|
+
|
644
|
+
def verify_ui_states(ui_states)
|
645
|
+
ui_states.each do |ui_object, object_states|
|
646
|
+
object_states.each do |property, state|
|
647
|
+
case property
|
648
|
+
when :class
|
649
|
+
actual = ui_object.get_attribute(:class)
|
650
|
+
when :exists
|
651
|
+
actual = ui_object.exists?
|
652
|
+
when :enabled
|
653
|
+
actual = ui_object.enabled?
|
654
|
+
when :disabled
|
655
|
+
actual = ui_object.disabled?
|
656
|
+
when :visible
|
657
|
+
actual = ui_object.visible?
|
658
|
+
when :hidden
|
659
|
+
actual = ui_object.hidden?
|
660
|
+
when :displayed
|
661
|
+
actual = ui_object.displayed?
|
662
|
+
when :width
|
663
|
+
actual = ui_object.width
|
664
|
+
when :height
|
665
|
+
actual = ui_object.height
|
666
|
+
when :x
|
667
|
+
actual = ui_object.x
|
668
|
+
when :y
|
669
|
+
actual = ui_object.y
|
670
|
+
when :readonly
|
671
|
+
actual = ui_object.read_only?
|
672
|
+
when :checked
|
673
|
+
actual = ui_object.checked?
|
674
|
+
when :selected
|
675
|
+
actual = ui_object.selected?
|
676
|
+
when :value, :caption
|
677
|
+
actual = ui_object.get_value
|
678
|
+
when :maxlength
|
679
|
+
actual = ui_object.get_max_length
|
680
|
+
when :rowcount
|
681
|
+
actual = ui_object.get_row_count
|
682
|
+
when :columncount
|
683
|
+
actual = ui_object.get_column_count
|
684
|
+
when :placeholder
|
685
|
+
actual = ui_object.get_placeholder
|
686
|
+
when :min
|
687
|
+
actual = ui_object.get_min
|
688
|
+
when :max
|
689
|
+
actual = ui_object.get_max
|
690
|
+
when :step
|
691
|
+
actual = ui_object.get_step
|
692
|
+
when :options, :items, :list_items
|
693
|
+
actual = ui_object.get_list_items
|
694
|
+
when :optioncount, :itemcount
|
695
|
+
actual = ui_object.get_item_count
|
696
|
+
when :all_items, :all_list_items
|
697
|
+
actual = ui_object.get_all_list_items
|
698
|
+
when :all_items_count
|
699
|
+
actual = ui_object.get_all_items_count
|
700
|
+
when :column_headers
|
701
|
+
actual = ui_object.get_header_columns
|
702
|
+
when :siebel_options
|
703
|
+
actual = ui_object.get_siebel_options
|
704
|
+
else
|
705
|
+
if property.is_a?(Hash)
|
706
|
+
property.each do |key, value|
|
707
|
+
case key
|
708
|
+
when :cell
|
709
|
+
actual = ui_object.get_table_cell(value[0].to_i, value[1].to_i)
|
710
|
+
when :row
|
711
|
+
actual = ui_object.get_table_row(value.to_i)
|
712
|
+
when :column
|
713
|
+
actual = ui_object.get_table_column(value.to_i)
|
714
|
+
when :item
|
715
|
+
actual = ui_object.get_list_item(value.to_i)
|
716
|
+
when :attribute
|
717
|
+
actual = ui_object.get_attribute(value)
|
718
|
+
when :native_attribute
|
719
|
+
actual = ui_object.get_native_attribute(value)
|
720
|
+
end
|
721
|
+
end
|
722
|
+
else
|
723
|
+
props = property.to_s.split('_')
|
724
|
+
case props[0].to_sym
|
725
|
+
when :cell
|
726
|
+
cell = property.to_s.delete('cell_')
|
727
|
+
cell = cell.split('_')
|
728
|
+
actual = ui_object.get_table_cell(cell[0].to_i, cell[1].to_i)
|
729
|
+
when :row
|
730
|
+
row = property.to_s.delete('row_')
|
731
|
+
actual = ui_object.get_table_row(row.to_i)
|
732
|
+
when :column
|
733
|
+
column = property.to_s.delete('column_')
|
734
|
+
actual = ui_object.get_table_column(column.to_i)
|
735
|
+
when :item
|
736
|
+
item = property.to_s.delete('item_')
|
737
|
+
actual = ui_object.get_list_item(item.to_i)
|
738
|
+
end
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
if state.is_a?(Hash) && state.length == 1
|
743
|
+
error_msg = "Expected UI object '#{ui_object.get_name}' (#{ui_object.get_locator}) #{property} property to"
|
744
|
+
state.each do |key, value|
|
745
|
+
case key
|
746
|
+
when :lt, :less_than
|
747
|
+
ExceptionQueue.enqueue_exception("#{error_msg} be less than #{value} but found '#{actual}'") unless actual < value
|
748
|
+
when :lt_eq, :less_than_or_equal
|
749
|
+
ExceptionQueue.enqueue_exception("#{error_msg} be less than or equal to #{value} but found '#{actual}'") unless actual <= value
|
750
|
+
when :gt, :greater_than
|
751
|
+
ExceptionQueue.enqueue_exception("#{error_msg} be greater than #{value} but found '#{actual}'") unless actual > value
|
752
|
+
when :gt_eq, :greater_than_or_equal
|
753
|
+
ExceptionQueue.enqueue_exception("#{error_msg} be greater than or equal to #{value} but found '#{actual}'") unless actual >= value
|
754
|
+
when :starts_with
|
755
|
+
ExceptionQueue.enqueue_exception("#{error_msg} start with '#{value}' but found '#{actual}'") unless actual.start_with?(value)
|
756
|
+
when :ends_with
|
757
|
+
ExceptionQueue.enqueue_exception("#{error_msg} end with '#{value}' but found '#{actual}'") unless actual.end_with?(value)
|
758
|
+
when :contains
|
759
|
+
ExceptionQueue.enqueue_exception("#{error_msg} contain '#{value}' but found '#{actual}'") unless actual.include?(value)
|
760
|
+
when :not_contains, :does_not_contain
|
761
|
+
ExceptionQueue.enqueue_exception("#{error_msg} not contain '#{value}' but found '#{actual}'") if actual.include?(value)
|
762
|
+
when :not_equal
|
763
|
+
ExceptionQueue.enqueue_exception("#{error_msg} not equal '#{value}' but found '#{actual}'") if actual == value
|
764
|
+
when :like, :is_like
|
765
|
+
actual_like = actual.delete("\n")
|
766
|
+
actual_like = actual_like.delete("\r")
|
767
|
+
actual_like = actual_like.delete("\t")
|
768
|
+
actual_like = actual_like.delete(' ')
|
769
|
+
actual_like = actual_like.downcase
|
770
|
+
expected = value.delete("\n")
|
771
|
+
expected = expected.delete("\r")
|
772
|
+
expected = expected.delete("\t")
|
773
|
+
expected = expected.delete(' ')
|
774
|
+
expected = expected.downcase
|
775
|
+
ExceptionQueue.enqueue_exception("#{error_msg} be like '#{value}' but found '#{actual}'") unless actual_like.include?(expected)
|
776
|
+
when :translate
|
777
|
+
expected = I18n.t(value)
|
778
|
+
ExceptionQueue.enqueue_assert_equal(expected, actual, "Expected UI object '#{ui_object.get_name}' (#{ui_object.get_locator}) translated #{property} property")
|
779
|
+
end
|
780
|
+
end
|
781
|
+
else
|
782
|
+
ExceptionQueue.enqueue_assert_equal(state, actual, "Expected UI object '#{ui_object.get_name}' (#{ui_object.get_locator}) #{property} property")
|
783
|
+
end
|
784
|
+
end
|
785
|
+
end
|
786
|
+
ExceptionQueue.post_exceptions
|
787
|
+
end
|
788
|
+
|
789
|
+
# Populate the specified UI elements in this Section object with the associated data from a Hash passed as an
|
790
|
+
# argument. Data values must be in the form of a String for textfield and select list controls. For checkbox
|
791
|
+
# and radio buttons, data must either be a Boolean or a String that evaluates to a Boolean value (Yes, No, 1,
|
792
|
+
# 0, true, false).
|
793
|
+
#
|
794
|
+
# The optional wait_time parameter is used to specify the time (in seconds) to wait for each UI element to become
|
795
|
+
# visible before entering the associated data value. This option is useful in situations where entering data, or
|
796
|
+
# setting the state of a UI element might cause other UI elements to become visible or active. Specifying a wait_time
|
797
|
+
# value ensures that the subsequent UI elements will be ready to be interacted with as states are changed. If the wait
|
798
|
+
# time is nil, then the wait time will be 5 seconds.
|
799
|
+
#
|
800
|
+
# To delete all text content in a text field, pass !DELETE as the data to be entered.
|
801
|
+
#
|
802
|
+
# @param data [Hash] UI element(s) and associated data to be entered
|
803
|
+
# @param wait_time [Integer] wait time in seconds
|
804
|
+
# @example
|
805
|
+
# data = { prefix_select => 'Mr.',
|
806
|
+
# first_name_field => 'Ignatious',
|
807
|
+
# last_name_field => 'Snickelfritz',
|
808
|
+
# gender_select => 'Male',
|
809
|
+
# dob_field => '12/14/1957',
|
810
|
+
# organ_donor_check => 'Yes',
|
811
|
+
# dnr_on_file_check => 'Yes'
|
812
|
+
# }
|
813
|
+
# populate_data_fields(data)
|
814
|
+
#
|
815
|
+
def populate_data_fields(data, wait_time = nil)
|
816
|
+
timeout = wait_time.nil? ? 5 : wait_time
|
817
|
+
data.each do |data_field, data_param|
|
818
|
+
unless data_param.blank?
|
819
|
+
# make sure the intended UI target element is visible before trying to set its value
|
820
|
+
data_field.wait_until_visible(timeout)
|
821
|
+
if data_param == '!DELETE'
|
822
|
+
data_field.clear
|
823
|
+
else
|
824
|
+
case data_field.get_object_type
|
825
|
+
when :checkbox
|
826
|
+
data_field.set_checkbox_state(data_param.to_bool)
|
827
|
+
when :selectlist
|
828
|
+
data_field.get_siebel_object_type == 'JComboBox' ?
|
829
|
+
data_field.set("#{data_param}\t") :
|
830
|
+
data_field.choose_option(data_param)
|
831
|
+
when :radio
|
832
|
+
data_field.set_selected_state(data_param.to_bool)
|
833
|
+
when :textfield
|
834
|
+
data_field.set("#{data_param}\t")
|
835
|
+
when :section
|
836
|
+
data_field.set(data_param)
|
837
|
+
end
|
838
|
+
end
|
839
|
+
end
|
840
|
+
end
|
841
|
+
end
|
842
|
+
|
843
|
+
def get_attribute(attrib)
|
844
|
+
section, = find_section
|
845
|
+
raise "Section object '#{get_name}' (#{get_locator}) not found" unless section
|
846
|
+
section[attrib]
|
847
|
+
end
|
848
|
+
|
849
|
+
def get_native_attribute(attrib)
|
850
|
+
section, = find_section
|
851
|
+
raise "Section object '#{get_name}' (#{get_locator}) not found" unless section
|
852
|
+
section.native.attribute(attrib)
|
853
|
+
end
|
854
|
+
|
855
|
+
private
|
856
|
+
|
857
|
+
def find_section
|
858
|
+
locator = get_locator
|
859
|
+
locator = locator.gsub('|', ' ')
|
860
|
+
obj = page.find(@locator_type, locator, :wait => 0.1)
|
861
|
+
[obj, @locator_type]
|
862
|
+
rescue
|
863
|
+
[nil, nil]
|
864
|
+
end
|
865
|
+
end
|
866
|
+
end
|