testcentricity_web 4.1.5 → 4.1.8
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 +4 -4
- data/.gitignore +18 -0
- data/.simplecov +9 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile.lock +60 -2
- data/README.md +17 -7
- data/Rakefile +86 -1
- data/config/cucumber.yml +170 -0
- data/config/test_data/LOCAL_data.json +15 -0
- data/config/test_data/LOCAL_data.xls +0 -0
- data/config/test_data/LOCAL_data.yml +11 -0
- data/config/test_data/data.json +25 -0
- data/config/test_data/data.xls +0 -0
- data/config/test_data/data.yml +20 -0
- data/docker-compose-v3.yml +48 -0
- data/features/basic_form_page_css.feature +39 -0
- data/features/basic_form_page_xpath.feature +26 -0
- data/features/media_players.feature +84 -0
- data/features/step_definitions/generic_steps.rb.rb +109 -0
- data/features/step_definitions/media_steps.rb +30 -0
- data/features/support/data/form_data.rb +43 -0
- data/features/support/env.rb +50 -0
- data/features/support/hooks.rb +265 -0
- data/features/support/pages/base_test_page.rb +22 -0
- data/features/support/pages/basic_css_form_page.rb +52 -0
- data/features/support/pages/basic_form_page.rb +393 -0
- data/features/support/pages/basic_xpath_form_page.rb +53 -0
- data/features/support/pages/custom_controls_page.rb +13 -0
- data/features/support/pages/indexed_sections_page.rb +13 -0
- data/features/support/pages/media_test_page.rb +208 -0
- data/features/support/sections/header_nav.rb +39 -0
- data/features/support/world_data.rb +12 -0
- data/features/support/world_pages.rb +18 -0
- data/lib/testcentricity_web/appium_server.rb +5 -0
- data/lib/testcentricity_web/data_objects/data_objects_helper.rb +7 -0
- data/lib/testcentricity_web/data_objects/environment.rb +18 -0
- data/lib/testcentricity_web/data_objects/excel_helper.rb +60 -59
- data/lib/testcentricity_web/version.rb +1 -1
- data/lib/testcentricity_web/web_core/drag_drop_helper.rb +4 -0
- data/lib/testcentricity_web/web_core/page_object.rb +8 -8
- data/lib/testcentricity_web/web_core/page_objects_helper.rb +43 -8
- data/lib/testcentricity_web/web_core/page_section.rb +1 -16
- data/lib/testcentricity_web/web_core/webdriver_helper.rb +16 -0
- data/lib/testcentricity_web/web_elements/checkbox.rb +3 -4
- data/lib/testcentricity_web/web_elements/file_field.rb +9 -5
- data/lib/testcentricity_web/web_elements/image.rb +2 -1
- data/lib/testcentricity_web/web_elements/media.rb +45 -11
- data/lib/testcentricity_web/web_elements/radio.rb +5 -1
- data/lib/testcentricity_web/web_elements/select_list.rb +11 -3
- data/lib/testcentricity_web/web_elements/ui_elements_helper.rb +33 -7
- data/lib/testcentricity_web/web_elements/video.rb +2 -2
- data/reports/.keep +1 -0
- data/test_site/basic_test_page.html +290 -0
- data/test_site/custom_controls_page.html +58 -0
- data/test_site/images/Granny.jpg +0 -0
- data/test_site/images/Wilder.jpg +0 -0
- data/test_site/images/You_Betcha.jpg +0 -0
- data/test_site/indexed_sections_page.html +58 -0
- data/test_site/media/MIB2-subtitles-pt-BR.vtt +49 -0
- data/test_site/media/MIB2.mp4 +0 -0
- data/test_site/media/MP4_small.mp4 +0 -0
- data/test_site/media/MPS_sample.mp3 +0 -0
- data/test_site/media/count_and_bars.mp4 +0 -0
- data/test_site/media_page.html +86 -0
- data/testcentricity_web.gemspec +5 -0
- metadata +131 -4
- data/test_site/test_page.html +0 -11
@@ -0,0 +1,208 @@
|
|
1
|
+
# Page Object class definition for Media Test page with CSS locators
|
2
|
+
|
3
|
+
class MediaTestPage < BaseTestPage
|
4
|
+
trait(:page_name) { 'Media Test' }
|
5
|
+
trait(:page_locator) { 'div.media-page-body' }
|
6
|
+
trait(:page_url) { '/media_page.html' }
|
7
|
+
trait(:navigator) { header_nav.open_media_page }
|
8
|
+
trait(:page_title) { 'Media Page'}
|
9
|
+
|
10
|
+
# Media Test page UI elements
|
11
|
+
videos video_player_1: 'video#video_player1'
|
12
|
+
audios audio_player: 'audio#audio_player'
|
13
|
+
|
14
|
+
def verify_page_ui
|
15
|
+
super
|
16
|
+
|
17
|
+
preload = case
|
18
|
+
when Environ.browser == :safari
|
19
|
+
'auto'
|
20
|
+
when Environ.device_os == :ios && Environ.driver != :webdriver
|
21
|
+
'auto'
|
22
|
+
when %i[firefox firefox_headless].include?(Environ.browser)
|
23
|
+
''
|
24
|
+
else
|
25
|
+
'metadata'
|
26
|
+
end
|
27
|
+
video_player_1.wait_until_ready_state_is(4, 10)
|
28
|
+
audio_player.wait_until_ready_state_is(4, 10)
|
29
|
+
ui = {
|
30
|
+
video_player_1 => {
|
31
|
+
visible: true,
|
32
|
+
paused: true,
|
33
|
+
autoplay: false,
|
34
|
+
loop: false,
|
35
|
+
ended: false,
|
36
|
+
controls: true,
|
37
|
+
current_time: 0,
|
38
|
+
ready_state: 4,
|
39
|
+
default_playback_rate: 1,
|
40
|
+
playback_rate: 1,
|
41
|
+
seeking: false,
|
42
|
+
default_muted: false,
|
43
|
+
muted: false,
|
44
|
+
volume: 1,
|
45
|
+
preload: preload,
|
46
|
+
poster: '',
|
47
|
+
src: '',
|
48
|
+
duration: 17.6
|
49
|
+
},
|
50
|
+
audio_player => {
|
51
|
+
visible: true,
|
52
|
+
paused: true,
|
53
|
+
autoplay: false,
|
54
|
+
loop: false,
|
55
|
+
ended: false,
|
56
|
+
controls: true,
|
57
|
+
current_time: 0,
|
58
|
+
ready_state: 4,
|
59
|
+
default_playback_rate: 1,
|
60
|
+
playback_rate: 1,
|
61
|
+
seeking: false,
|
62
|
+
default_muted: false,
|
63
|
+
muted: false,
|
64
|
+
volume: 1,
|
65
|
+
preload: preload,
|
66
|
+
src: '',
|
67
|
+
duration: 3.45
|
68
|
+
}
|
69
|
+
}
|
70
|
+
verify_ui_states(ui)
|
71
|
+
unless Environ.browser == :safari
|
72
|
+
ui = { video_player_1 => {
|
73
|
+
width: video_player_1.video_width,
|
74
|
+
height: video_player_1.video_height
|
75
|
+
}
|
76
|
+
}
|
77
|
+
verify_ui_states(ui)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def perform_action(media_type, action)
|
82
|
+
player = dispatch_player(media_type)
|
83
|
+
case action.downcase.to_sym
|
84
|
+
when :play
|
85
|
+
player.play
|
86
|
+
player.send_keys(:enter) if player.paused?
|
87
|
+
player.click_at(25, 25) if player.paused?
|
88
|
+
when :pause
|
89
|
+
player.pause
|
90
|
+
when :mute
|
91
|
+
player.mute
|
92
|
+
when :unmute
|
93
|
+
player.unmute
|
94
|
+
else
|
95
|
+
raise "#{action} is not a valid selector"
|
96
|
+
end
|
97
|
+
sleep(2)
|
98
|
+
end
|
99
|
+
|
100
|
+
def verify_media_state(media_type, state)
|
101
|
+
player = dispatch_player(media_type)
|
102
|
+
duration = player.duration
|
103
|
+
props = case state.downcase.to_sym
|
104
|
+
when :playing
|
105
|
+
{
|
106
|
+
visible: true,
|
107
|
+
paused: false,
|
108
|
+
ended: false,
|
109
|
+
current_time: { greater_than: 0 },
|
110
|
+
seeking: false
|
111
|
+
}
|
112
|
+
when :paused
|
113
|
+
{
|
114
|
+
visible: true,
|
115
|
+
paused: true,
|
116
|
+
ended: false,
|
117
|
+
current_time: { greater_than: 0 },
|
118
|
+
seeking: false
|
119
|
+
}
|
120
|
+
when :ended
|
121
|
+
{
|
122
|
+
visible: true,
|
123
|
+
paused: true,
|
124
|
+
ended: true,
|
125
|
+
current_time: duration,
|
126
|
+
seeking: false
|
127
|
+
}
|
128
|
+
when :muted
|
129
|
+
{
|
130
|
+
visible: true,
|
131
|
+
muted: true
|
132
|
+
}
|
133
|
+
when :unmuted
|
134
|
+
{
|
135
|
+
visible: true,
|
136
|
+
muted: false
|
137
|
+
}
|
138
|
+
else
|
139
|
+
raise "#{state} is not a valid selector"
|
140
|
+
end
|
141
|
+
verify_ui_states(player => props)
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_playback_rate(media_type, rate)
|
145
|
+
player = dispatch_player(media_type)
|
146
|
+
player.playback_rate = rate.to_f
|
147
|
+
reset_play(player)
|
148
|
+
end
|
149
|
+
|
150
|
+
def verify_playback_rate(media_type, rate)
|
151
|
+
player = dispatch_player(media_type)
|
152
|
+
ui = {
|
153
|
+
player => {
|
154
|
+
playback_rate: rate.to_f,
|
155
|
+
visible: true,
|
156
|
+
paused: false,
|
157
|
+
current_time: { greater_than: 0 },
|
158
|
+
seeking: false
|
159
|
+
}
|
160
|
+
}
|
161
|
+
verify_ui_states(ui)
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_volume(media_type, volume)
|
165
|
+
player = dispatch_player(media_type)
|
166
|
+
player.volume = volume.to_f
|
167
|
+
reset_play(player)
|
168
|
+
end
|
169
|
+
|
170
|
+
def verify_volume(media_type, volume)
|
171
|
+
player = dispatch_player(media_type)
|
172
|
+
ui = {
|
173
|
+
player => {
|
174
|
+
volume: volume.to_f,
|
175
|
+
visible: true,
|
176
|
+
muted: false,
|
177
|
+
paused: false,
|
178
|
+
current_time: { greater_than: 0 },
|
179
|
+
seeking: false
|
180
|
+
}
|
181
|
+
}
|
182
|
+
verify_ui_states(ui)
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
def dispatch_player(media_type)
|
188
|
+
player = case media_type.downcase.to_sym
|
189
|
+
when :video
|
190
|
+
video_player_1
|
191
|
+
when :audio
|
192
|
+
audio_player
|
193
|
+
else
|
194
|
+
raise "#{media_type} is not a valid selector"
|
195
|
+
end
|
196
|
+
player.wait_until_ready_state_is(4, 10)
|
197
|
+
player
|
198
|
+
end
|
199
|
+
|
200
|
+
def reset_play(player)
|
201
|
+
player.pause
|
202
|
+
player.current_time = 0
|
203
|
+
player.play
|
204
|
+
player.send_keys(:enter) if player.paused?
|
205
|
+
player.click_at(25, 25) if player.paused?
|
206
|
+
sleep(1)
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Section Object class definition for Top Navigation Bar
|
2
|
+
|
3
|
+
class NavHeader < TestCentricity::PageSection
|
4
|
+
trait(:section_locator) { 'div#nav_bar' }
|
5
|
+
trait(:section_name) { 'Top Navigation Bar' }
|
6
|
+
|
7
|
+
# Top Navigation Bar UI elements
|
8
|
+
links form_link: 'a#form_link',
|
9
|
+
media_link: 'a#media_link',
|
10
|
+
indexed_sections_link: 'a#indexed_sections_link',
|
11
|
+
custom_controls_link: 'a#custom_controls_link'
|
12
|
+
|
13
|
+
def open_form_page
|
14
|
+
form_link.click
|
15
|
+
end
|
16
|
+
|
17
|
+
def open_media_page
|
18
|
+
media_link.click
|
19
|
+
end
|
20
|
+
|
21
|
+
def open_indexed_sections_page
|
22
|
+
indexed_sections_link.click
|
23
|
+
end
|
24
|
+
|
25
|
+
def open_custom_controls_page
|
26
|
+
custom_controls_link.click
|
27
|
+
end
|
28
|
+
|
29
|
+
def verify_nav_bar
|
30
|
+
ui = {
|
31
|
+
self => { exists: true, visible: true, class: 'topnav' },
|
32
|
+
form_link => { visible: true, caption: 'Basic HTML Form' },
|
33
|
+
media_link => { visible: true, caption: 'Media' },
|
34
|
+
indexed_sections_link => { visible: true, caption: 'Indexed Sections' },
|
35
|
+
custom_controls_link => { visible: true, caption: 'Custom Controls' },
|
36
|
+
}
|
37
|
+
verify_ui_states(ui)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module WorldData
|
2
|
+
#
|
3
|
+
# data_objects method returns a hash table of your web app's data objects and associated data object classes to be instantiated
|
4
|
+
# by the TestCentricity™ DataManager. Data Object class definitions are contained in the features/support/data folder.
|
5
|
+
#
|
6
|
+
def data_objects
|
7
|
+
{ form_data_source: FormDataSource }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
World(WorldData)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module WorldPages
|
2
|
+
#
|
3
|
+
# page_objects method returns a hash table of your web app's page objects and associated page classes to be instantiated
|
4
|
+
# by the TestCentricity™ PageManager. Page Object class definitions are contained in the features/support/pages folder.
|
5
|
+
#
|
6
|
+
def page_objects
|
7
|
+
{
|
8
|
+
basic_css_form_page: BasicCSSFormPage,
|
9
|
+
basic_xpath_form_page: BasicXpathFormPage,
|
10
|
+
media_test_page: MediaTestPage,
|
11
|
+
indexed_sections_page: IndexedSectionsPage,
|
12
|
+
custom_controls_page: CustomControlsPage
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
World(WorldPages)
|
@@ -18,10 +18,12 @@ module TestCentricity
|
|
18
18
|
def start
|
19
19
|
# terminate any currently running Appium Server
|
20
20
|
if running?
|
21
|
+
# :nocov:
|
21
22
|
system('killall -9 node')
|
22
23
|
puts 'Terminating existing Appium Server'
|
23
24
|
sleep(5)
|
24
25
|
puts 'Appium Server is being restarted'
|
26
|
+
# :nocov:
|
25
27
|
else
|
26
28
|
puts 'Appium Server is starting'
|
27
29
|
end
|
@@ -56,6 +58,7 @@ module TestCentricity
|
|
56
58
|
|
57
59
|
private
|
58
60
|
|
61
|
+
# :nocov:
|
59
62
|
def parameters
|
60
63
|
cmd = ['appium']
|
61
64
|
@params.each do |key, value|
|
@@ -64,5 +67,7 @@ module TestCentricity
|
|
64
67
|
end
|
65
68
|
cmd
|
66
69
|
end
|
70
|
+
# :nocov:
|
67
71
|
end
|
68
72
|
end
|
73
|
+
|
@@ -52,6 +52,7 @@ module TestCentricity
|
|
52
52
|
@current = current
|
53
53
|
end
|
54
54
|
|
55
|
+
# :nocov:
|
55
56
|
def to_hash(node_name = nil)
|
56
57
|
data = {}
|
57
58
|
if node_name.nil?
|
@@ -79,9 +80,12 @@ module TestCentricity
|
|
79
80
|
def write_json_data(file_name, mode, node_name = nil)
|
80
81
|
File.open(file_name, mode) { |file| file.write(to_json(node_name)) }
|
81
82
|
end
|
83
|
+
|
84
|
+
# :nocov:
|
82
85
|
end
|
83
86
|
|
84
87
|
|
88
|
+
# :nocov:
|
85
89
|
class DataSource
|
86
90
|
attr_accessor :file_path
|
87
91
|
attr_accessor :node
|
@@ -135,6 +139,7 @@ module TestCentricity
|
|
135
139
|
result.to_s
|
136
140
|
end
|
137
141
|
end
|
142
|
+
# :nocov:
|
138
143
|
|
139
144
|
|
140
145
|
class ExcelDataSource < TestCentricity::DataSource
|
@@ -159,6 +164,7 @@ module TestCentricity
|
|
159
164
|
ExcelData.read_row_data(pick_excel_data_source(sheet, @row_spec), sheet, @row_spec)
|
160
165
|
end
|
161
166
|
|
167
|
+
# :nocov:
|
162
168
|
def read_excel_pool_data(sheet, row_name, parallel = false)
|
163
169
|
@row_spec = parallel == :parallel && ENV['PARALLEL'] ? "#{row_name}#{ENV['TEST_ENV_NUMBER']}" : row_name
|
164
170
|
ExcelData.read_row_from_pool(pick_excel_data_source(sheet, row_name), sheet, @row_spec)
|
@@ -173,6 +179,7 @@ module TestCentricity
|
|
173
179
|
@row_spec = parallel == :parallel && ENV['PARALLEL'] ? "#{row_name}#{ENV['TEST_ENV_NUMBER']}" : row_name
|
174
180
|
ExcelData.write_row_data(pick_excel_data_source(sheet, @row_spec), sheet, @row_spec, row_data)
|
175
181
|
end
|
182
|
+
# :nocov:
|
176
183
|
end
|
177
184
|
end
|
178
185
|
|
@@ -71,6 +71,7 @@ module TestCentricity
|
|
71
71
|
@screen_shots = []
|
72
72
|
|
73
73
|
attr_accessor :test_environment
|
74
|
+
attr_accessor :app_host
|
74
75
|
attr_accessor :browser
|
75
76
|
attr_accessor :browser_size
|
76
77
|
attr_accessor :headless
|
@@ -133,9 +134,20 @@ module TestCentricity
|
|
133
134
|
@db_username = data['DB_USERNAME']
|
134
135
|
@db_password = data['DB_PASSWORD']
|
135
136
|
|
137
|
+
url = @hostname.blank? ? "#{@base_url}#{@append}" : "#{@hostname}/#{@base_url}#{@append}"
|
138
|
+
@app_host = if @user_id.blank? || @password.blank?
|
139
|
+
"#{@protocol}://#{url}"
|
140
|
+
else
|
141
|
+
"#{@protocol}://#{@user_id}:#{@password}@#{url}"
|
142
|
+
end
|
143
|
+
|
136
144
|
super
|
137
145
|
end
|
138
146
|
|
147
|
+
def self.app_host
|
148
|
+
@app_host
|
149
|
+
end
|
150
|
+
|
139
151
|
def self.session_code
|
140
152
|
if @session_code.nil?
|
141
153
|
characters = ('a'..'z').to_a
|
@@ -328,6 +340,10 @@ module TestCentricity
|
|
328
340
|
@platform = platform
|
329
341
|
end
|
330
342
|
|
343
|
+
def self.platform
|
344
|
+
@platform
|
345
|
+
end
|
346
|
+
|
331
347
|
def self.is_mobile?
|
332
348
|
@platform == :mobile
|
333
349
|
end
|
@@ -380,6 +396,7 @@ module TestCentricity
|
|
380
396
|
@screen_shots = []
|
381
397
|
end
|
382
398
|
|
399
|
+
# :nocov:
|
383
400
|
def self.report_header
|
384
401
|
report_header = "\n<b><u>TEST ENVIRONMENT</u>:</b> #{ENV['TEST_ENVIRONMENT']}\n"\
|
385
402
|
" <b>Browser:</b>\t #{Environ.browser.capitalize}\n"
|
@@ -395,6 +412,7 @@ module TestCentricity
|
|
395
412
|
report_header = "#{report_header} <b>WCAG Accessibility Standard:</b>\t #{ENV['ACCESSIBILITY_STANDARD']}\n" if ENV['ACCESSIBILITY_STANDARD']
|
396
413
|
"#{report_header}\n\n"
|
397
414
|
end
|
415
|
+
# :nocov:
|
398
416
|
end
|
399
417
|
end
|
400
418
|
|
@@ -52,6 +52,65 @@ module TestCentricity
|
|
52
52
|
exists
|
53
53
|
end
|
54
54
|
|
55
|
+
def self.read_row_data(file, sheet, row_spec, columns = nil)
|
56
|
+
raise "File #{file} does not exists" unless File.exist?(file)
|
57
|
+
work_book = Spreadsheet.open(file)
|
58
|
+
work_sheet = work_book.worksheet(sheet)
|
59
|
+
# get column headings from row 0 of worksheet
|
60
|
+
headings = work_sheet.row(0)
|
61
|
+
# if row_spec is a string then we have to find a matching row name
|
62
|
+
if row_spec.is_a? String
|
63
|
+
column_number = 0
|
64
|
+
found = false
|
65
|
+
headings.each do |heading|
|
66
|
+
if heading == 'ROW_NAME'
|
67
|
+
found = true
|
68
|
+
break
|
69
|
+
end
|
70
|
+
column_number += 1
|
71
|
+
end
|
72
|
+
raise "Could not find a column named ROW_NAME in worksheet #{sheet}" unless found
|
73
|
+
# find first cell in ROW_NAME column containing a string that matches the row_spec parameter
|
74
|
+
found = false
|
75
|
+
row_number = 0
|
76
|
+
work_sheet.each do |row|
|
77
|
+
if row[column_number] == row_spec
|
78
|
+
found = true
|
79
|
+
break
|
80
|
+
end
|
81
|
+
row_number += 1
|
82
|
+
end
|
83
|
+
raise "Could not find a row named '#{row_spec}' in worksheet #{sheet}" unless found
|
84
|
+
data = work_sheet.row(row_number)
|
85
|
+
# if row_spec is a number then ensure that it doesn't exceed the number of available rows
|
86
|
+
elsif row_spec.is_a? Numeric
|
87
|
+
raise "Row # #{row_spec} is greater than number of rows in worksheet #{sheet}" if row_spec > work_sheet.last_row_index
|
88
|
+
data = work_sheet.row(row_spec)
|
89
|
+
end
|
90
|
+
|
91
|
+
# if no columns have been specified, return all columns
|
92
|
+
columns = headings if columns.nil?
|
93
|
+
# create results hash table
|
94
|
+
result = Hash.new
|
95
|
+
columns.each do |column|
|
96
|
+
column_number = 0
|
97
|
+
found = false
|
98
|
+
headings.each do |heading|
|
99
|
+
if column == heading
|
100
|
+
value = data[column_number].to_s
|
101
|
+
value = calculate_dynamic_value(value) if value.start_with? 'eval!'
|
102
|
+
result[column] = value
|
103
|
+
found = true
|
104
|
+
break
|
105
|
+
end
|
106
|
+
column_number += 1
|
107
|
+
end
|
108
|
+
raise "Could not find a column named '#{column}' in worksheet #{sheet}" unless found
|
109
|
+
end
|
110
|
+
result
|
111
|
+
end
|
112
|
+
|
113
|
+
# :nocov:
|
55
114
|
def self.read_row_from_pool(file, sheet, row_spec, columns = nil)
|
56
115
|
raise "File #{file} does not exists" unless File.exist?(file)
|
57
116
|
work_book = Spreadsheet.open(file)
|
@@ -118,64 +177,6 @@ module TestCentricity
|
|
118
177
|
read_row_data(file, sheet, new_row, columns)
|
119
178
|
end
|
120
179
|
|
121
|
-
def self.read_row_data(file, sheet, row_spec, columns = nil)
|
122
|
-
raise "File #{file} does not exists" unless File.exist?(file)
|
123
|
-
work_book = Spreadsheet.open(file)
|
124
|
-
work_sheet = work_book.worksheet(sheet)
|
125
|
-
# get column headings from row 0 of worksheet
|
126
|
-
headings = work_sheet.row(0)
|
127
|
-
# if row_spec is a string then we have to find a matching row name
|
128
|
-
if row_spec.is_a? String
|
129
|
-
column_number = 0
|
130
|
-
found = false
|
131
|
-
headings.each do |heading|
|
132
|
-
if heading == 'ROW_NAME'
|
133
|
-
found = true
|
134
|
-
break
|
135
|
-
end
|
136
|
-
column_number += 1
|
137
|
-
end
|
138
|
-
raise "Could not find a column named ROW_NAME in worksheet #{sheet}" unless found
|
139
|
-
# find first cell in ROW_NAME column containing a string that matches the row_spec parameter
|
140
|
-
found = false
|
141
|
-
row_number = 0
|
142
|
-
work_sheet.each do |row|
|
143
|
-
if row[column_number] == row_spec
|
144
|
-
found = true
|
145
|
-
break
|
146
|
-
end
|
147
|
-
row_number += 1
|
148
|
-
end
|
149
|
-
raise "Could not find a row named '#{row_spec}' in worksheet #{sheet}" unless found
|
150
|
-
data = work_sheet.row(row_number)
|
151
|
-
# if row_spec is a number then ensure that it doesn't exceed the number of available rows
|
152
|
-
elsif row_spec.is_a? Numeric
|
153
|
-
raise "Row # #{row_spec} is greater than number of rows in worksheet #{sheet}" if row_spec > work_sheet.last_row_index
|
154
|
-
data = work_sheet.row(row_spec)
|
155
|
-
end
|
156
|
-
|
157
|
-
# if no columns have been specified, return all columns
|
158
|
-
columns = headings if columns.nil?
|
159
|
-
# create results hash table
|
160
|
-
result = Hash.new
|
161
|
-
columns.each do |column|
|
162
|
-
column_number = 0
|
163
|
-
found = false
|
164
|
-
headings.each do |heading|
|
165
|
-
if column == heading
|
166
|
-
value = data[column_number].to_s
|
167
|
-
value = calculate_dynamic_value(value) if value.start_with? 'eval!'
|
168
|
-
result[column] = value
|
169
|
-
found = true
|
170
|
-
break
|
171
|
-
end
|
172
|
-
column_number += 1
|
173
|
-
end
|
174
|
-
raise "Could not find a column named '#{column}' in worksheet #{sheet}" unless found
|
175
|
-
end
|
176
|
-
result
|
177
|
-
end
|
178
|
-
|
179
180
|
def self.read_range_data(file, sheet, range_spec)
|
180
181
|
raise "File #{file} does not exists" unless File.exist?(file)
|
181
182
|
work_book = Spreadsheet.open(file)
|
@@ -270,6 +271,6 @@ module TestCentricity
|
|
270
271
|
# rename new Excel document, replacing the original
|
271
272
|
File.rename(outfile, file)
|
272
273
|
end
|
274
|
+
# :nocov:
|
273
275
|
end
|
274
276
|
end
|
275
|
-
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# :nocov:
|
2
|
+
|
1
3
|
module CapybaraExtension
|
2
4
|
def drag_by(right_by, down_by)
|
3
5
|
base.drag_by(right_by, down_by)
|
@@ -11,5 +13,7 @@ module CapybaraSeleniumExtension
|
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
16
|
+
# :nocov:
|
17
|
+
|
14
18
|
::Capybara::Selenium::Node.send :include, CapybaraSeleniumExtension
|
15
19
|
::Capybara::Node::Element.send :include, CapybaraExtension
|
@@ -1,5 +1,9 @@
|
|
1
1
|
module TestCentricity
|
2
2
|
class PageObject < BasePageSectionObject
|
3
|
+
def initialize
|
4
|
+
set_locator_type(page_locator) if defined?(page_locator)
|
5
|
+
end
|
6
|
+
|
3
7
|
# Declare and instantiate a single generic UI Element for this page object.
|
4
8
|
#
|
5
9
|
# @param element_name [Symbol] name of UI object (as a symbol)
|
@@ -308,19 +312,15 @@ module TestCentricity
|
|
308
312
|
end
|
309
313
|
|
310
314
|
def open_portal
|
311
|
-
|
312
|
-
url = environment.hostname.blank? ? "#{environment.base_url}#{environment.append}" : "#{environment.hostname}/#{environment.base_url}#{environment.append}"
|
313
|
-
if environment.user_id.blank? || environment.password.blank?
|
314
|
-
visit "#{environment.protocol}://#{url}"
|
315
|
-
else
|
316
|
-
visit "#{environment.protocol}://#{environment.user_id}:#{environment.password}@#{url}"
|
317
|
-
end
|
315
|
+
visit Environ.current.app_host
|
318
316
|
Environ.portal_state = :open
|
319
317
|
end
|
320
318
|
|
321
319
|
def verify_page_exists
|
322
320
|
raise "Page object #{self.class.name} does not have a page_locator trait defined" unless defined?(page_locator)
|
323
|
-
|
321
|
+
|
322
|
+
set_locator_type(page_locator) if @locator_type.blank?
|
323
|
+
unless page.has_selector?(@locator_type, page_locator)
|
324
324
|
body_class = find(:xpath, '//body')[:class]
|
325
325
|
error_message = %(
|
326
326
|
Expected page to have selector '#{page_locator}' but found '#{body_class}' instead.
|
@@ -6,6 +6,26 @@ module TestCentricity
|
|
6
6
|
include Capybara::Node::Matchers
|
7
7
|
include Test::Unit::Assertions
|
8
8
|
|
9
|
+
attr_accessor :locator_type
|
10
|
+
|
11
|
+
XPATH_SELECTORS = ['//', '[@', '[contains(']
|
12
|
+
CSS_SELECTORS = ['#', ':nth-child(', ':first-child', ':last-child', ':nth-of-type(', ':first-of-type', ':last-of-type', '^=', '$=', '*=', ':contains(']
|
13
|
+
|
14
|
+
def set_locator_type(locator = nil)
|
15
|
+
locator = @locator if locator.nil?
|
16
|
+
is_xpath = XPATH_SELECTORS.any? { |selector| locator.include?(selector) }
|
17
|
+
is_css = CSS_SELECTORS.any? { |selector| locator.include?(selector) }
|
18
|
+
@locator_type = if is_xpath && !is_css
|
19
|
+
:xpath
|
20
|
+
elsif is_css && !is_xpath
|
21
|
+
:css
|
22
|
+
elsif !is_css && !is_xpath
|
23
|
+
:css
|
24
|
+
else
|
25
|
+
:css
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
9
29
|
# Define a trait for this page or section object.
|
10
30
|
#
|
11
31
|
# @param trait_name [Symbol] name of trait (as a symbol)
|
@@ -31,6 +51,8 @@ module TestCentricity
|
|
31
51
|
ui_object.get_attribute(:name)
|
32
52
|
when :title
|
33
53
|
ui_object.title
|
54
|
+
when :secure
|
55
|
+
ui_object.secure?
|
34
56
|
when :exists
|
35
57
|
ui_object.exists?
|
36
58
|
when :enabled
|
@@ -43,6 +65,8 @@ module TestCentricity
|
|
43
65
|
ui_object.hidden?
|
44
66
|
when :displayed
|
45
67
|
ui_object.displayed?
|
68
|
+
when :obscured
|
69
|
+
ui_object.obscured?
|
46
70
|
when :focused
|
47
71
|
ui_object.focused?
|
48
72
|
when :width
|
@@ -63,6 +87,8 @@ module TestCentricity
|
|
63
87
|
ui_object.indeterminate?
|
64
88
|
when :value, :caption
|
65
89
|
ui_object.get_value
|
90
|
+
when :required
|
91
|
+
ui_object.required?
|
66
92
|
when :maxlength
|
67
93
|
ui_object.get_max_length
|
68
94
|
when :rowcount
|
@@ -241,8 +267,11 @@ module TestCentricity
|
|
241
267
|
end
|
242
268
|
|
243
269
|
# Populate the specified UI elements on this page or section object with the associated data from a Hash passed as an
|
244
|
-
# argument. Data values must be in the form of a String for textfield and
|
245
|
-
# buttons, data must either be a Boolean or a String that evaluates to a Boolean value (Yes, No, 1, 0, true,
|
270
|
+
# argument. Data values must be in the form of a String for textfield, selectlist, and filefield controls. For checkbox
|
271
|
+
# and radio buttons, data must either be a Boolean or a String that evaluates to a Boolean value (Yes, No, 1, 0, true,
|
272
|
+
# false). For range controls, data must be an Integer. For input(type='color') color picker controls, which are specified
|
273
|
+
# as a textfield, data must be in the form of a hex color String. For section objects, data values must be a String, and
|
274
|
+
# the section object must have a set method defined.
|
246
275
|
#
|
247
276
|
# The optional wait_time parameter is used to specify the time (in seconds) to wait for each UI element to become
|
248
277
|
# visible before entering the associated data value. This option is useful in situations where entering data, or
|
@@ -285,14 +314,20 @@ module TestCentricity
|
|
285
314
|
check_state = data_param.is_a?(String) ? data_param.to_bool : data_param
|
286
315
|
data_field.set_selected_state(check_state)
|
287
316
|
when :textfield
|
288
|
-
data_field.
|
289
|
-
|
290
|
-
|
291
|
-
data_field.
|
292
|
-
data_field.
|
317
|
+
if %w[color number].include?(data_field.get_attribute(:type))
|
318
|
+
data_field.set(data_param)
|
319
|
+
else
|
320
|
+
data_field.set("#{data_param}\t")
|
321
|
+
if integrity_check && data_field.get_value != data_param
|
322
|
+
data_field.set('')
|
323
|
+
data_field.send_keys(data_param)
|
324
|
+
data_field.send_keys(:tab)
|
325
|
+
end
|
293
326
|
end
|
294
|
-
when :section
|
327
|
+
when :section, :range
|
295
328
|
data_field.set(data_param)
|
329
|
+
when :filefield
|
330
|
+
data_field.file_upload(data_param)
|
296
331
|
end
|
297
332
|
end
|
298
333
|
end
|