testcentricity_web 4.1.4 → 4.1.7
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 +5 -0
- data/CHANGELOG.md +43 -1
- data/Gemfile.lock +60 -2
- data/README.md +166 -21
- data/Rakefile +60 -1
- data/config/cucumber.yml +163 -0
- data/config/test_data/data.json +12 -0
- data/config/test_data/data.xls +0 -0
- data/config/test_data/data.yml +12 -0
- data/docker-compose-v3.yml +48 -0
- data/features/basic_test_page_css.feature +35 -0
- data/features/basic_test_page_xpath.feature +27 -0
- data/features/media_players.feature +87 -0
- data/features/step_definitions/generic_steps.rb.rb +44 -0
- data/features/step_definitions/media_steps.rb +30 -0
- data/features/support/env.rb +50 -0
- data/features/support/hooks.rb +245 -0
- data/features/support/pages/base_test_page.rb +11 -0
- data/features/support/pages/basic_css_test_page.rb +52 -0
- data/features/support/pages/basic_test_page.rb +297 -0
- data/features/support/pages/basic_xpath_test_page.rb +53 -0
- data/features/support/pages/custom_controls_page.rb +16 -0
- data/features/support/pages/indexed_sections_page.rb +16 -0
- data/features/support/pages/media_test_page.rb +195 -0
- data/features/support/sections/header_nav.rb +28 -0
- data/features/support/world_data.rb +12 -0
- data/features/support/world_pages.rb +18 -0
- data/lib/testcentricity_web/data_objects/environment.rb +16 -0
- data/lib/testcentricity_web/version.rb +1 -1
- data/lib/testcentricity_web/web_core/page_object.rb +8 -8
- data/lib/testcentricity_web/web_core/page_objects_helper.rb +41 -8
- data/lib/testcentricity_web/web_core/page_section.rb +1 -16
- data/lib/testcentricity_web/web_core/webdriver_helper.rb +77 -120
- data/lib/testcentricity_web/web_elements/checkbox.rb +3 -4
- data/lib/testcentricity_web/web_elements/media.rb +50 -4
- data/lib/testcentricity_web/web_elements/radio.rb +5 -1
- data/lib/testcentricity_web/web_elements/select_list.rb +18 -7
- data/lib/testcentricity_web/web_elements/ui_elements_helper.rb +13 -0
- 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_page.html +86 -0
- data/testcentricity_web.gemspec +5 -0
- metadata +125 -4
- data/test_site/test_page.html +0 -11
@@ -0,0 +1,195 @@
|
|
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
|
+
|
9
|
+
# Media Test page UI elements
|
10
|
+
videos video_player: 'video#video_player'
|
11
|
+
audios audio_player: 'audio#audio_player'
|
12
|
+
|
13
|
+
def verify_page_ui
|
14
|
+
preload = case
|
15
|
+
when Environ.browser == :safari || Environ.device_os == :ios
|
16
|
+
'auto'
|
17
|
+
when Environ.browser == :firefox
|
18
|
+
''
|
19
|
+
else
|
20
|
+
'metadata'
|
21
|
+
end
|
22
|
+
video_player.wait_until_ready_state_is(4, 10)
|
23
|
+
ui = {
|
24
|
+
self => { exists: true, secure: false, title: 'Media Page' },
|
25
|
+
header_label => { visible: true, caption: 'Media Page' },
|
26
|
+
video_player => {
|
27
|
+
visible: true,
|
28
|
+
paused: true,
|
29
|
+
autoplay: false,
|
30
|
+
loop: false,
|
31
|
+
ended: false,
|
32
|
+
controls: true,
|
33
|
+
current_time: 0,
|
34
|
+
ready_state: 4,
|
35
|
+
default_playback_rate: 1,
|
36
|
+
playback_rate: 1,
|
37
|
+
seeking: false,
|
38
|
+
default_muted: false,
|
39
|
+
muted: false,
|
40
|
+
volume: 1,
|
41
|
+
preload: preload,
|
42
|
+
poster: '',
|
43
|
+
src: '',
|
44
|
+
duration: 17.6
|
45
|
+
},
|
46
|
+
audio_player => {
|
47
|
+
visible: true,
|
48
|
+
paused: true,
|
49
|
+
autoplay: false,
|
50
|
+
loop: false,
|
51
|
+
ended: false,
|
52
|
+
controls: true,
|
53
|
+
current_time: 0,
|
54
|
+
ready_state: 4,
|
55
|
+
default_playback_rate: 1,
|
56
|
+
playback_rate: 1,
|
57
|
+
seeking: false,
|
58
|
+
default_muted: false,
|
59
|
+
muted: false,
|
60
|
+
volume: 1,
|
61
|
+
preload: preload,
|
62
|
+
src: '',
|
63
|
+
duration: 3.45
|
64
|
+
}
|
65
|
+
}
|
66
|
+
verify_ui_states(ui)
|
67
|
+
end
|
68
|
+
|
69
|
+
def perform_action(media_type, action)
|
70
|
+
player = dispatch_player(media_type)
|
71
|
+
case action.downcase.to_sym
|
72
|
+
when :play
|
73
|
+
player.play
|
74
|
+
player.send_keys(:enter) if player.paused?
|
75
|
+
player.click if player.paused?
|
76
|
+
when :pause
|
77
|
+
player.pause
|
78
|
+
when :mute
|
79
|
+
player.mute
|
80
|
+
when :unmute
|
81
|
+
player.unmute
|
82
|
+
else
|
83
|
+
raise "#{action} is not a valid selector"
|
84
|
+
end
|
85
|
+
sleep(2)
|
86
|
+
end
|
87
|
+
|
88
|
+
def verify_media_state(media_type, state)
|
89
|
+
player = dispatch_player(media_type)
|
90
|
+
duration = player.duration
|
91
|
+
props = case state.downcase.to_sym
|
92
|
+
when :playing
|
93
|
+
{
|
94
|
+
visible: true,
|
95
|
+
paused: false,
|
96
|
+
ended: false,
|
97
|
+
current_time: { greater_than: 0 },
|
98
|
+
seeking: false
|
99
|
+
}
|
100
|
+
when :paused
|
101
|
+
{
|
102
|
+
visible: true,
|
103
|
+
paused: true,
|
104
|
+
ended: false,
|
105
|
+
current_time: { greater_than: 0 },
|
106
|
+
seeking: false
|
107
|
+
}
|
108
|
+
when :ended
|
109
|
+
{
|
110
|
+
visible: true,
|
111
|
+
paused: true,
|
112
|
+
ended: true,
|
113
|
+
current_time: duration,
|
114
|
+
seeking: false
|
115
|
+
}
|
116
|
+
when :muted
|
117
|
+
{
|
118
|
+
visible: true,
|
119
|
+
muted: true
|
120
|
+
}
|
121
|
+
when :unmuted
|
122
|
+
{
|
123
|
+
visible: true,
|
124
|
+
muted: false
|
125
|
+
}
|
126
|
+
else
|
127
|
+
raise "#{state} is not a valid selector"
|
128
|
+
end
|
129
|
+
verify_ui_states(player => props)
|
130
|
+
end
|
131
|
+
|
132
|
+
def set_playback_rate(media_type, rate)
|
133
|
+
player = dispatch_player(media_type)
|
134
|
+
player.playback_rate = rate.to_f
|
135
|
+
reset_play(player)
|
136
|
+
end
|
137
|
+
|
138
|
+
def verify_playback_rate(media_type, rate)
|
139
|
+
player = dispatch_player(media_type)
|
140
|
+
ui = {
|
141
|
+
player => {
|
142
|
+
playback_rate: rate.to_f,
|
143
|
+
visible: true,
|
144
|
+
paused: false,
|
145
|
+
current_time: { greater_than: 0 },
|
146
|
+
seeking: false
|
147
|
+
}
|
148
|
+
}
|
149
|
+
verify_ui_states(ui)
|
150
|
+
end
|
151
|
+
|
152
|
+
def set_volume(media_type, volume)
|
153
|
+
player = dispatch_player(media_type)
|
154
|
+
player.volume = volume.to_f
|
155
|
+
reset_play(player)
|
156
|
+
end
|
157
|
+
|
158
|
+
def verify_volume(media_type, volume)
|
159
|
+
player = dispatch_player(media_type)
|
160
|
+
ui = {
|
161
|
+
player => {
|
162
|
+
volume: volume.to_f,
|
163
|
+
visible: true,
|
164
|
+
muted: false,
|
165
|
+
paused: false,
|
166
|
+
current_time: { greater_than: 0 },
|
167
|
+
seeking: false
|
168
|
+
}
|
169
|
+
}
|
170
|
+
verify_ui_states(ui)
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def dispatch_player(media_type)
|
176
|
+
player = case media_type.downcase.to_sym
|
177
|
+
when :video
|
178
|
+
video_player
|
179
|
+
when :audio
|
180
|
+
audio_player
|
181
|
+
else
|
182
|
+
raise "#{media_type} is not a valid selector"
|
183
|
+
end
|
184
|
+
player.wait_until_ready_state_is(4, 10)
|
185
|
+
player
|
186
|
+
end
|
187
|
+
|
188
|
+
def reset_play(player)
|
189
|
+
player.pause
|
190
|
+
player.current_time = 0
|
191
|
+
player.play
|
192
|
+
player.send_keys(:enter) if player.paused?
|
193
|
+
sleep(1)
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
+
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
|
+
{}
|
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_test_page: BasicCSSTestPage,
|
9
|
+
basic_xpath_test_page: BasicXpathTestPage,
|
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)
|
@@ -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
|
@@ -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
|
@@ -63,6 +85,8 @@ module TestCentricity
|
|
63
85
|
ui_object.indeterminate?
|
64
86
|
when :value, :caption
|
65
87
|
ui_object.get_value
|
88
|
+
when :required
|
89
|
+
ui_object.required?
|
66
90
|
when :maxlength
|
67
91
|
ui_object.get_max_length
|
68
92
|
when :rowcount
|
@@ -241,8 +265,11 @@ module TestCentricity
|
|
241
265
|
end
|
242
266
|
|
243
267
|
# 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,
|
268
|
+
# argument. Data values must be in the form of a String for textfield, selectlist, and filefield controls. For checkbox
|
269
|
+
# and radio buttons, data must either be a Boolean or a String that evaluates to a Boolean value (Yes, No, 1, 0, true,
|
270
|
+
# false). For range controls, data must be an Integer. For input(type='color') color picker controls, which are specified
|
271
|
+
# as a textfield, data must be in the form of a hex color String. For section objects, data values must be a String, and
|
272
|
+
# the section object must have a set method defined.
|
246
273
|
#
|
247
274
|
# The optional wait_time parameter is used to specify the time (in seconds) to wait for each UI element to become
|
248
275
|
# visible before entering the associated data value. This option is useful in situations where entering data, or
|
@@ -285,14 +312,20 @@ module TestCentricity
|
|
285
312
|
check_state = data_param.is_a?(String) ? data_param.to_bool : data_param
|
286
313
|
data_field.set_selected_state(check_state)
|
287
314
|
when :textfield
|
288
|
-
data_field.
|
289
|
-
|
290
|
-
|
291
|
-
data_field.
|
292
|
-
data_field.
|
315
|
+
if data_field.get_attribute(:type) == 'color'
|
316
|
+
data_field.set(data_param)
|
317
|
+
else
|
318
|
+
data_field.set("#{data_param}\t")
|
319
|
+
if integrity_check && data_field.get_value != data_param
|
320
|
+
data_field.set('')
|
321
|
+
data_field.send_keys(data_param)
|
322
|
+
data_field.send_keys(:tab)
|
323
|
+
end
|
293
324
|
end
|
294
|
-
when :section
|
325
|
+
when :section, :range
|
295
326
|
data_field.set(data_param)
|
327
|
+
when :filefield
|
328
|
+
data_field.file_upload(data_param)
|
296
329
|
end
|
297
330
|
end
|
298
331
|
end
|
@@ -5,10 +5,6 @@ module TestCentricity
|
|
5
5
|
attr_accessor :parent
|
6
6
|
attr_accessor :parent_list
|
7
7
|
attr_accessor :list_index
|
8
|
-
attr_accessor :locator_type
|
9
|
-
|
10
|
-
XPATH_SELECTORS = ['//', '[@', '[contains(']
|
11
|
-
CSS_SELECTORS = ['#', ':nth-child(', ':first-child', ':last-child', ':nth-of-type(', ':first-of-type', ':last-of-type', '^=', '$=', '*=', ':contains(']
|
12
8
|
|
13
9
|
def initialize(name, parent, locator, context)
|
14
10
|
@name = name
|
@@ -17,18 +13,7 @@ module TestCentricity
|
|
17
13
|
@context = context
|
18
14
|
@parent_list = nil
|
19
15
|
@list_index = nil
|
20
|
-
|
21
|
-
is_xpath = XPATH_SELECTORS.any? { |selector| @locator.include?(selector) }
|
22
|
-
is_css = CSS_SELECTORS.any? { |selector| @locator.include?(selector) }
|
23
|
-
@locator_type = if is_xpath && !is_css
|
24
|
-
:xpath
|
25
|
-
elsif is_css && !is_xpath
|
26
|
-
:css
|
27
|
-
elsif !is_css && !is_xpath
|
28
|
-
:css
|
29
|
-
else
|
30
|
-
:css
|
31
|
-
end
|
16
|
+
set_locator_type
|
32
17
|
end
|
33
18
|
|
34
19
|
def get_locator
|