testcentricity_mobile 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +105 -0
- data/LICENSE.md +27 -0
- data/README.md +1795 -0
- data/lib/testcentricity_mobile/app_core/appium_connect_helper.rb +634 -0
- data/lib/testcentricity_mobile/app_core/screen_object.rb +413 -0
- data/lib/testcentricity_mobile/app_core/screen_objects_helper.rb +183 -0
- data/lib/testcentricity_mobile/app_core/screen_section.rb +607 -0
- data/lib/testcentricity_mobile/app_elements/alert.rb +152 -0
- data/lib/testcentricity_mobile/app_elements/app_element.rb +692 -0
- data/lib/testcentricity_mobile/app_elements/button.rb +10 -0
- data/lib/testcentricity_mobile/app_elements/checkbox.rb +56 -0
- data/lib/testcentricity_mobile/app_elements/image.rb +10 -0
- data/lib/testcentricity_mobile/app_elements/label.rb +10 -0
- data/lib/testcentricity_mobile/app_elements/list.rb +175 -0
- data/lib/testcentricity_mobile/app_elements/switch.rb +66 -0
- data/lib/testcentricity_mobile/app_elements/textfield.rb +51 -0
- data/lib/testcentricity_mobile/appium_server.rb +71 -0
- data/lib/testcentricity_mobile/data_objects/data_objects_helper.rb +100 -0
- data/lib/testcentricity_mobile/data_objects/environment.rb +416 -0
- data/lib/testcentricity_mobile/exception_queue_helper.rb +160 -0
- data/lib/testcentricity_mobile/utility_helpers.rb +48 -0
- data/lib/testcentricity_mobile/version.rb +3 -0
- data/lib/testcentricity_mobile/world_extensions.rb +61 -0
- data/lib/testcentricity_mobile.rb +99 -0
- metadata +317 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module TestCentricity
|
2
|
+
module AppElements
|
3
|
+
class AppCheckBox < AppUIElement
|
4
|
+
def initialize(name, parent, locator, context)
|
5
|
+
super
|
6
|
+
@type = :checkbox
|
7
|
+
end
|
8
|
+
|
9
|
+
# Is checkbox checked?
|
10
|
+
#
|
11
|
+
# @return [Boolean]
|
12
|
+
# @example
|
13
|
+
# remember_me_checkbox.checked?
|
14
|
+
#
|
15
|
+
def checked?
|
16
|
+
obj = element
|
17
|
+
object_not_found_exception(obj)
|
18
|
+
obj.selected?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Set the check state of a checkbox object.
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# remember_me_checkbox.check
|
25
|
+
#
|
26
|
+
def check
|
27
|
+
set_checkbox_state(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Uncheck a checkbox object.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# remember_me_checkbox.uncheck
|
34
|
+
#
|
35
|
+
def uncheck
|
36
|
+
set_checkbox_state(false)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the check state of a checkbox object.
|
40
|
+
#
|
41
|
+
# @param state [Boolean] true = checked / false = unchecked
|
42
|
+
# @example
|
43
|
+
# remember_me_checkbox.set_checkbox_state(true)
|
44
|
+
#
|
45
|
+
def set_checkbox_state(state)
|
46
|
+
obj = element
|
47
|
+
object_not_found_exception(obj)
|
48
|
+
if state
|
49
|
+
obj.click unless obj.selected?
|
50
|
+
else
|
51
|
+
obj.click if obj.selected?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module TestCentricity
|
2
|
+
module AppElements
|
3
|
+
class AppList < AppUIElement
|
4
|
+
attr_accessor :list_item
|
5
|
+
attr_accessor :scrolling
|
6
|
+
attr_accessor :item_objects
|
7
|
+
|
8
|
+
def initialize(name, parent, locator, context)
|
9
|
+
super
|
10
|
+
@type = :list
|
11
|
+
@list_item = nil
|
12
|
+
@scrolling = :vertical
|
13
|
+
@item_objects = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def define_list_elements(element_spec)
|
17
|
+
element_spec.each do |element, value|
|
18
|
+
case element
|
19
|
+
when :list_item
|
20
|
+
@list_item = value
|
21
|
+
when :scrolling
|
22
|
+
@scrolling = value
|
23
|
+
else
|
24
|
+
raise "#{element} is not a recognized list element"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def scroll_mode
|
30
|
+
obj = element
|
31
|
+
object_not_found_exception(obj)
|
32
|
+
@scrolling
|
33
|
+
end
|
34
|
+
|
35
|
+
def item_refs
|
36
|
+
obj = element
|
37
|
+
object_not_found_exception(obj)
|
38
|
+
@item_objects
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return the number of items in a list object.
|
42
|
+
#
|
43
|
+
# @return [Integer]
|
44
|
+
# @example
|
45
|
+
# num_nav_items = nav_list.get_item_count
|
46
|
+
#
|
47
|
+
def get_item_count
|
48
|
+
obj = element
|
49
|
+
object_not_found_exception(obj)
|
50
|
+
if Environ.is_ios? && obj.attribute(:type) == 'XCUIElementTypePickerWheel'
|
51
|
+
raise 'This method is not supported for XCUIElementTypePickerWheel controls'
|
52
|
+
end
|
53
|
+
list_loc = get_list_item_locator
|
54
|
+
items = obj.find_elements(list_loc.keys[0], list_loc.values[0])
|
55
|
+
if items.size > 0 && Environ.is_android?
|
56
|
+
start_count = items.count
|
57
|
+
direction = @scrolling == :horizontal ? :right : :down
|
58
|
+
scroll_count = 0
|
59
|
+
loop do
|
60
|
+
save_count = items.count
|
61
|
+
swipe_gesture(direction)
|
62
|
+
scroll_count += 1
|
63
|
+
obj.find_elements(list_loc.keys[0], list_loc.values[0]).each do |item|
|
64
|
+
items.push(item) unless items.include?(item)
|
65
|
+
end
|
66
|
+
break if items.count == save_count
|
67
|
+
end
|
68
|
+
direction = @scrolling == :horizontal ? :left : :up
|
69
|
+
scroll_count.times do
|
70
|
+
swipe_gesture(direction)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
@item_objects = items if Environ.is_android? && start_count < items.count
|
74
|
+
items.count
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return array of strings of all items in a list object.
|
78
|
+
#
|
79
|
+
# @return [Array]
|
80
|
+
# @example
|
81
|
+
# nav_items = nav_list.get_options
|
82
|
+
#
|
83
|
+
def get_list_items
|
84
|
+
list_items = []
|
85
|
+
obj = element
|
86
|
+
object_not_found_exception(obj)
|
87
|
+
if Environ.is_ios? && obj.attribute(:type) == 'XCUIElementTypePickerWheel'
|
88
|
+
raise 'This method is not supported for XCUIElementTypePickerWheel controls'
|
89
|
+
end
|
90
|
+
list_loc = get_list_item_locator
|
91
|
+
items = obj.find_elements(list_loc.keys[0], list_loc.values[0])
|
92
|
+
items.each do |item|
|
93
|
+
list_items.push(item.text)
|
94
|
+
end
|
95
|
+
list_items
|
96
|
+
end
|
97
|
+
|
98
|
+
# Select the specified item in a list object. Accepts a String or Integer.
|
99
|
+
#
|
100
|
+
# @param item [String, Integer] text or index of item to select
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# province_list.choose_item(2)
|
104
|
+
# province_list.choose_item('Manitoba')
|
105
|
+
#
|
106
|
+
def choose_item(item)
|
107
|
+
obj = element
|
108
|
+
object_not_found_exception(obj)
|
109
|
+
if Environ.is_ios? && obj.attribute(:type) == 'XCUIElementTypePickerWheel'
|
110
|
+
obj.send_keys(item)
|
111
|
+
else
|
112
|
+
list_loc = get_list_item_locator
|
113
|
+
items = obj.find_elements(list_loc.keys[0], list_loc.values[0])
|
114
|
+
if item.is_a?(Integer)
|
115
|
+
items[item - 1].click
|
116
|
+
else
|
117
|
+
items.each do |list_item|
|
118
|
+
if list_item.text == item
|
119
|
+
list_item.click
|
120
|
+
break
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Wait until the list's item_count equals the specified value, or until the specified wait time has expired. If the
|
128
|
+
# wait time is nil, then the wait time will be Environ.default_max_wait_time.
|
129
|
+
#
|
130
|
+
# @param value [Integer or Hash] value expected or comparison hash
|
131
|
+
# @param seconds [Integer or Float] wait time in seconds
|
132
|
+
# @example
|
133
|
+
# search_results_list.wait_until_item_count_is(10, 15)
|
134
|
+
# or
|
135
|
+
# search_results_list.wait_until_item_count_is({ :greater_than_or_equal => 1 }, 5)
|
136
|
+
#
|
137
|
+
def wait_until_item_count_is(value, seconds = nil)
|
138
|
+
timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
|
139
|
+
wait = Selenium::WebDriver::Wait.new(timeout: timeout)
|
140
|
+
wait.until { compare(value, get_item_count) }
|
141
|
+
rescue
|
142
|
+
raise "Value of List #{object_ref_message} failed to equal '#{value}' after #{timeout} seconds" unless get_item_count == value
|
143
|
+
end
|
144
|
+
|
145
|
+
# Wait until the list's item_count changes, or until the specified wait time has expired. If the wait time is nil,
|
146
|
+
# then the wait time will be Environ.default_max_wait_time.
|
147
|
+
#
|
148
|
+
# @param seconds [Integer or Float] wait time in seconds
|
149
|
+
# @example
|
150
|
+
# search_results_list.wait_until_item_count_changes(10)
|
151
|
+
#
|
152
|
+
def wait_until_item_count_changes(seconds = nil)
|
153
|
+
value = get_item_count
|
154
|
+
timeout = seconds.nil? ? Environ.default_max_wait_time : seconds
|
155
|
+
wait = Selenium::WebDriver::Wait.new(timeout: timeout)
|
156
|
+
wait.until { get_item_count != value }
|
157
|
+
rescue
|
158
|
+
raise "Value of List #{object_ref_message} failed to change from '#{value}' after #{timeout} seconds" if get_item_count == value
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def get_list_item_locator
|
164
|
+
if @list_item.nil?
|
165
|
+
if Environ.device_os == :ios
|
166
|
+
define_list_elements({ :list_item => { class: 'XCUIElementTypeCell' } } )
|
167
|
+
else
|
168
|
+
define_list_elements({ :list_item => { class: 'android.widget.FrameLayout' } } )
|
169
|
+
end
|
170
|
+
end
|
171
|
+
@list_item
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module TestCentricity
|
2
|
+
module AppElements
|
3
|
+
class AppSwitch < AppUIElement
|
4
|
+
def initialize(name, parent, locator, context)
|
5
|
+
super
|
6
|
+
@type = :switch
|
7
|
+
end
|
8
|
+
|
9
|
+
# Is switch in ON state?
|
10
|
+
#
|
11
|
+
# @return [Boolean]
|
12
|
+
# @example
|
13
|
+
# use_face_id_switch.on?
|
14
|
+
#
|
15
|
+
def on?
|
16
|
+
obj = element
|
17
|
+
object_not_found_exception(obj)
|
18
|
+
|
19
|
+
if Environ.is_ios?
|
20
|
+
state = obj.attribute(:value)
|
21
|
+
state.to_bool
|
22
|
+
else
|
23
|
+
state = obj.attribute(:checked)
|
24
|
+
state.boolean? ? state : state == 'true'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
alias checked? on?
|
29
|
+
alias selected? on?
|
30
|
+
|
31
|
+
# Set the state of a switch object to ON.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# use_face_id_switch.on
|
35
|
+
#
|
36
|
+
def on
|
37
|
+
obj = element
|
38
|
+
object_not_found_exception(obj)
|
39
|
+
obj.click unless on?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Set the state of a switch object to OFF.
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# use_face_id_switch.off
|
46
|
+
#
|
47
|
+
def off
|
48
|
+
obj = element
|
49
|
+
object_not_found_exception(obj)
|
50
|
+
obj.click if on?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Set the ON/OFF state of a switch object.
|
54
|
+
#
|
55
|
+
# @param state [Boolean] true = on / false = off
|
56
|
+
# @example
|
57
|
+
# use_face_id_switch.set_switch_state(true)
|
58
|
+
#
|
59
|
+
def set_switch_state(state)
|
60
|
+
obj = element
|
61
|
+
object_not_found_exception(obj)
|
62
|
+
state ? on : off
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module TestCentricity
|
2
|
+
module AppElements
|
3
|
+
class AppTextField < AppUIElement
|
4
|
+
def initialize(name, parent, locator, context)
|
5
|
+
super
|
6
|
+
@type = :textfield
|
7
|
+
end
|
8
|
+
|
9
|
+
# Is text field set to read-only?
|
10
|
+
#
|
11
|
+
# @return [Boolean]
|
12
|
+
# @example
|
13
|
+
# comments_field.read_only?
|
14
|
+
#
|
15
|
+
def read_only?
|
16
|
+
obj = element
|
17
|
+
object_not_found_exception(obj)
|
18
|
+
!!obj.attribute('readonly')
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return maxlength character count of a text field.
|
22
|
+
#
|
23
|
+
# @return [Integer]
|
24
|
+
# @example
|
25
|
+
# max_num_chars = comments_field.get_max_length
|
26
|
+
#
|
27
|
+
def get_max_length
|
28
|
+
obj = element
|
29
|
+
object_not_found_exception(obj)
|
30
|
+
max_length = obj.attribute('maxlength')
|
31
|
+
max_length.to_i unless max_length.blank?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Return placeholder text of a text field.
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
# @example
|
38
|
+
# placeholder_message = username_field.get_placeholder
|
39
|
+
#
|
40
|
+
def get_placeholder
|
41
|
+
obj = element
|
42
|
+
object_not_found_exception(obj)
|
43
|
+
if AppiumConnect.is_webview?
|
44
|
+
obj.attribute('placeholder')
|
45
|
+
else
|
46
|
+
obj.text
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'childprocess'
|
2
|
+
|
3
|
+
#
|
4
|
+
# This class is designed to start and stop the Appium Server process. In order to use it you must have Appium
|
5
|
+
# and node installed on your computer. You can pass parameters to Appium at startup via the constructor.
|
6
|
+
#
|
7
|
+
module TestCentricity
|
8
|
+
class AppiumServer
|
9
|
+
attr_accessor :process
|
10
|
+
|
11
|
+
def initialize(params = {})
|
12
|
+
@params = params
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Start the Appium Server
|
17
|
+
#
|
18
|
+
def start
|
19
|
+
# terminate any currently running Appium Server
|
20
|
+
if running?
|
21
|
+
system('killall -9 node')
|
22
|
+
puts 'Terminating existing Appium Server'
|
23
|
+
sleep(5)
|
24
|
+
puts 'Appium Server is being restarted'
|
25
|
+
else
|
26
|
+
puts 'Appium Server is starting'
|
27
|
+
end
|
28
|
+
# start new Appium Server
|
29
|
+
@process = ChildProcess.build(*parameters)
|
30
|
+
process.start
|
31
|
+
# wait until confirmation that Appium Server is running
|
32
|
+
wait = Selenium::WebDriver::Wait.new(timeout: 30)
|
33
|
+
wait.until { running? }
|
34
|
+
puts "Appium Server is running - PID = #{process.pid}"
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Check to see if Appium Server is running
|
39
|
+
#
|
40
|
+
def running?
|
41
|
+
response = false
|
42
|
+
begin
|
43
|
+
response = Net::HTTP.get_response(URI('http://0.0.0.0:4723/wd/hub/sessions'))
|
44
|
+
rescue
|
45
|
+
end
|
46
|
+
response && response.code_type == Net::HTTPOK
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Stop the Appium Server
|
51
|
+
#
|
52
|
+
def stop
|
53
|
+
process.stop
|
54
|
+
puts 'Appium Server has been terminated'
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# :nocov:
|
60
|
+
def parameters
|
61
|
+
cmd = ['appium']
|
62
|
+
@params.each do |key, value|
|
63
|
+
cmd << '--' + key.to_s
|
64
|
+
cmd << value.to_s if not value.nil? and value.size > 0
|
65
|
+
end
|
66
|
+
cmd
|
67
|
+
end
|
68
|
+
# :nocov:
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'json'
|
3
|
+
require 'virtus'
|
4
|
+
require 'time'
|
5
|
+
require 'chronic'
|
6
|
+
require 'faker'
|
7
|
+
|
8
|
+
|
9
|
+
module TestCentricity
|
10
|
+
|
11
|
+
PRIMARY_DATA_PATH ||= 'config/test_data/'
|
12
|
+
PRIMARY_DATA_FILE ||= "#{PRIMARY_DATA_PATH}data."
|
13
|
+
YML_PRIMARY_DATA_FILE ||= "#{PRIMARY_DATA_FILE}yml"
|
14
|
+
JSON_PRIMARY_DATA_FILE ||= "#{PRIMARY_DATA_FILE}json"
|
15
|
+
|
16
|
+
|
17
|
+
class DataObject
|
18
|
+
attr_accessor :current
|
19
|
+
attr_accessor :context
|
20
|
+
attr_accessor :hash_table
|
21
|
+
|
22
|
+
def initialize(data)
|
23
|
+
@hash_table = data
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.current
|
27
|
+
@current
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.current=(current)
|
31
|
+
@current = current
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
class DataPresenter
|
37
|
+
include Virtus.model
|
38
|
+
|
39
|
+
attr_accessor :current
|
40
|
+
attr_accessor :context
|
41
|
+
|
42
|
+
def initialize(data)
|
43
|
+
self.attributes = data unless data.nil?
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.current
|
47
|
+
@current
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.current=(current)
|
51
|
+
@current = current
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# :nocov:
|
57
|
+
class DataSource
|
58
|
+
attr_accessor :file_path
|
59
|
+
attr_accessor :node
|
60
|
+
|
61
|
+
def read_yaml_node_data(file_name, node_name)
|
62
|
+
@file_path = "#{PRIMARY_DATA_PATH}#{file_name}"
|
63
|
+
@node = node_name
|
64
|
+
data = YAML.load_file(@file_path)
|
65
|
+
data[node_name]
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_json_node_data(file_name, node_name)
|
69
|
+
@file_path = "#{PRIMARY_DATA_PATH}#{file_name}"
|
70
|
+
@node = node_name
|
71
|
+
raw_data = File.read(@file_path)
|
72
|
+
data = JSON.parse(raw_data)
|
73
|
+
data[node_name]
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.calculate_dynamic_value(value)
|
79
|
+
test_value = value.split('!', 2)
|
80
|
+
parameter = test_value[1].split('.', 2)
|
81
|
+
case parameter[0]
|
82
|
+
when 'Date'
|
83
|
+
result = eval("Chronic.parse('#{parameter[1]}')")
|
84
|
+
when 'FormattedDate', 'FormatDate'
|
85
|
+
date_time_params = parameter[1].split(' format! ', 2)
|
86
|
+
date_time = eval("Chronic.parse('#{date_time_params[0].strip}')")
|
87
|
+
result = date_time.to_s.format_date_time("#{date_time_params[1].strip}")
|
88
|
+
else
|
89
|
+
result = if Faker.constants.include?(parameter[0].to_sym)
|
90
|
+
eval("Faker::#{parameter[0]}.#{parameter[1]}")
|
91
|
+
else
|
92
|
+
eval(test_value[1])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
result.to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# :nocov:
|
99
|
+
end
|
100
|
+
|