testcentricity_mobile 4.1.7 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 301f1e2b30c7a80d9bdbfe2d03b29cdd7084ef45ff513a0ca545c2bf1a0c5759
4
- data.tar.gz: e6f1eb60c31387be3e1060829c7bb882d03db33b021312ac9e9ab46b06e3d547
3
+ metadata.gz: 4961bdc97a13ad7e08236fad109326f00a67a8d790a79257cb44691f1c435d61
4
+ data.tar.gz: 3b82d3cfb16db8d46264fb149ac8bf0d34866c437e5461e92f14b26697dfea09
5
5
  SHA512:
6
- metadata.gz: 349f6e1542be01c28d88a27b4883891c9fda7ed9aa5761d43bb17abfbd4b64b3393629de7282a082a77dff08aaa252efcc9f9dedef57e56945602776c9784a38
7
- data.tar.gz: 21b928e0920ef3de391ebeea78f0b3ac25bb1bc4f3f51a4c51eac3b1cbc7ed1deae1ad81e85f77e39132c993034ad435bde52cac1a5d3757d207ac9bf1cd865d
6
+ metadata.gz: 581d29d4727c72b30aa217a16cb91e542d05c183beba6f912788852b7dccebeb8ca3ea6d467298145509ea2efda4bc8115b31c8243dfbc6ac51bf2ad4da4baf4
7
+ data.tar.gz: 1a330c7e44dde5c2e727d02e47c4bf8d9048b6ccd2a6af0e129882533ce81564f8f9cccfc7ddedf6fc57175f96c3d5f6702332acfbcdca1430658c452ed5722e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
4
 
5
+ ## [4.1.8] - 13-JAN-2026
6
+
7
+ ### Added
8
+ * Added `activesupport` gem as a runtime dependency to prevent test failures if gem is not already installed.
9
+ * Added `DataSource.read_file` method capable of reading from `.yml`, `.json`, `.csv`, or `.xml` data files, and accepting
10
+ `options` hash for specifying hash key and value conversions to data being read prior to passing to `DataPresenter` objects.
11
+
12
+ ### Changed
13
+ * Refactored `EnvironData.read` method to allow passing `options` hash for specifying hash key and value conversions to
14
+ data being read prior to passing to `DataPresenter` objects.
15
+ * Refactored `ScreenObject.populate_data_fields` and `ScreenSection.populate_data_fields` methods to support passing a
16
+ `Symbol` for a UI element's name to support using `DataPresenter` object attributes to source data.
17
+ * Updated `appium_lib` gem to version 16.1.1.
18
+ * Updated `appium_lib_core` gem to version 11.2.0.
19
+
20
+ ### Removed
21
+ * Unused `DataObject` class has been removed.
22
+ * Removed unused `DataSource.read_yaml_node_data` and `DataSource.read_json_node_data` methods.
23
+
24
+
5
25
  ## [4.1.7] - 25-NOV-2025
6
26
 
7
27
  ### Changed
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2025, Tony Mrozinski
1
+ Copyright (c) 2014-2026, Tony Mrozinski
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
data/README.md CHANGED
@@ -95,12 +95,12 @@ A **Screen Object** is an object that represents a single screen in your AUT (Ap
95
95
  encapsulate the implementation details of a mobile app screen and expose an API that supports interaction with, and validation
96
96
  of the UI elements on the screen.
97
97
 
98
- **Screen Objects** makes it easier to maintain automated tests because changes to screen UI elements are updated in only
99
- one location - in the `ScreenObject` class definition. By adopting a **Screen Object Model**, Cucumber feature files and
100
- step definitions are no longer required to hold specific information about a screen's UI objects, thus minimizing maintenance
101
- requirements. If any element on, or property of a screen changes (text field attributes, button captions, element states,
102
- etc.), maintenance is performed in the `ScreenObject` class definition only, typically with no need to update the affected
103
- feature files, scenarios, or step definitions.
98
+ **Screen Objects** makes it easier to maintain automated tests because changes to a screen's UI elements are updated in
99
+ only one location - in the corresponding `ScreenObject` class definition. By adopting a **Screen Object Model**, Cucumber
100
+ feature files and step definitions are no longer required to hold specific information about a screen's UI objects, thus
101
+ minimizing maintenance requirements. If any element on, or property of a screen changes (text field attributes, button
102
+ captions, element states, etc.), maintenance is performed in the corresponding `ScreenObject` class definition only, typically
103
+ with no need to update the affected feature files, scenarios, or step definitions.
104
104
 
105
105
 
106
106
  ### Defining a ScreenObject
@@ -2010,7 +2010,7 @@ the `/features/support/<platform>/sections` folder, where `<platform>` is typica
2010
2010
  ---
2011
2011
  ## Copyright and License
2012
2012
 
2013
- TestCentricity™ Framework is Copyright (c) 2014-2025, Tony Mrozinski.
2013
+ TestCentricity™ Framework is Copyright (c) 2014-2026, Tony Mrozinski.
2014
2014
  All rights reserved.
2015
2015
 
2016
2016
  Redistribution and use in source and binary forms, with or without
@@ -51,6 +51,16 @@ module TestCentricity
51
51
  timeout = wait_time.nil? ? 2 : wait_time
52
52
  data.each do |data_field, data_param|
53
53
  unless data_param.blank?
54
+ # if data_field is a Symbol, find the corresponding object reference
55
+ if data_field.is_a?(Symbol)
56
+ begin
57
+ obj = method(data_field)
58
+ rescue
59
+ puts "No corresponding data field found for #{data_field}" if ENV['DEBUG']
60
+ next
61
+ end
62
+ data_field = obj.call
63
+ end
54
64
  # make sure the intended UI target element is visible before trying to set its value
55
65
  data_field.scroll_into_view unless data_field.wait_until_visible(timeout, post_exception = false)
56
66
  if data_param == '!DELETE'
@@ -1,47 +1,28 @@
1
- require 'yaml'
2
- require 'json'
3
- require 'virtus'
4
- require 'time'
1
+ require 'active_support'
2
+ require 'active_support/core_ext/hash'
5
3
  require 'chronic'
6
4
  require 'faker'
5
+ require 'json'
6
+ require 'rexml/document'
7
+ require 'smarter_csv'
8
+ require 'time'
9
+ require 'virtus'
10
+ require 'yaml'
7
11
 
8
12
 
9
13
  module TestCentricity
10
14
 
11
15
  PRIMARY_DATA_PATH ||= 'config/test_data/'
16
+ SECONDARY_DATA_PATH ||= 'config/data/'
12
17
  PRIMARY_DATA_FILE ||= "#{PRIMARY_DATA_PATH}data."
13
18
  YML_PRIMARY_DATA_FILE ||= "#{PRIMARY_DATA_FILE}yml"
14
19
  JSON_PRIMARY_DATA_FILE ||= "#{PRIMARY_DATA_FILE}json"
15
20
 
16
21
 
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
22
  class DataPresenter
37
23
  include Virtus.model
38
24
 
39
25
  attr_accessor :current
40
- attr_accessor :context
41
-
42
- def initialize(data)
43
- self.attributes = data unless data.nil?
44
- end
45
26
 
46
27
  def self.current
47
28
  @current
@@ -53,48 +34,162 @@ module TestCentricity
53
34
  end
54
35
 
55
36
 
56
- # :nocov:
57
37
  class DataSource
58
38
  attr_accessor :file_path
59
- attr_accessor :node
39
+ attr_accessor :file_extension
60
40
 
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]
41
+ def read_file(file_name, key = nil, node_name = nil, options = nil)
42
+ # check to see if full file path was specified
43
+ if File.exist?(file_name)
44
+ @file_path = file_name
45
+ else
46
+ # construct the full file path to the file to be read
47
+ @file_path = "#{PRIMARY_DATA_PATH}#{file_name}"
48
+ unless File.exist?(@file_path)
49
+ @file_path = "#{SECONDARY_DATA_PATH}#{file_name}"
50
+ raise "File #{file_name} not found in Primary or Secondary folders in config folder" unless File.exist?(@file_path)
51
+ end
52
+ end
53
+ # determine file type and read in data from file
54
+ @file_extension = File.extname(file_name)
55
+ data = case @file_extension
56
+ when '.yml'
57
+ YAML.load_file(@file_path)
58
+ when'.json'
59
+ JSON.parse(File.read(@file_path))
60
+ when '.xml'
61
+ xml_data = File.read(@file_path)
62
+ Hash.from_xml(xml_data)
63
+ when '.csv'
64
+ if options
65
+ SmarterCSV.process(@file_path, options)
66
+ else
67
+ SmarterCSV.process(@file_path)
68
+ end
69
+ else
70
+ raise "#{file_name} is not a supported file type"
71
+ end
72
+ # return data if sourced from a .csv file
73
+ return data if @file_extension == '.csv'
74
+
75
+ # read data from specified key and/or node
76
+ result = if key
77
+ raise "Key #{key} not found" unless data.key?(key)
78
+
79
+ if node_name
80
+ raise "Node #{node_name} not found" unless data[key].key?(node_name)
81
+
82
+ data[key][node_name]
83
+ else
84
+ data[key]
85
+ end
86
+ else
87
+ data
88
+ end
89
+ self.class.send(:process_data, result, options)
66
90
  end
67
91
 
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]
92
+ private
93
+
94
+ def self.process_data(data, options)
95
+ # calculate dynamic values if any are specified
96
+ if data.is_a?(Hash)
97
+ data.each do |key, value|
98
+ data[key] = calculate_dynamic_value(value) if value.to_s.start_with?('eval!')
99
+ end
100
+ end
101
+ return data unless options
102
+
103
+ # convert keys to symbols if :keys_as_symbols is true in options
104
+ if options.key?(:keys_as_symbols) && options[:keys_as_symbols]
105
+ data.transform_keys!(&:to_sym)
106
+ end
107
+ # convert values if :value_converters are specified in options
108
+ if options.key?(:value_converters)
109
+ map_values(data, options[:value_converters])
110
+ else
111
+ data
112
+ end
74
113
  end
75
114
 
76
- private
115
+ def self.map_values(data, value_converters)
116
+ data.each_with_object({}) do |(k, v), new_hash|
117
+ converter = value_converters[k]
118
+ v = converter.convert(v) if converter
119
+ new_hash[k] = v
120
+ end
121
+ end
77
122
 
78
123
  def self.calculate_dynamic_value(value)
79
124
  test_value = value.split('!', 2)
80
125
  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)
126
+ result = case parameter[0]
127
+ when 'Date'
128
+ Chronic.parse(parameter[1])
129
+ when 'FormattedDate', 'FormatDate'
130
+ date_time_params = parameter[1].split(' format! ', 2)
131
+ date_time = Chronic.parse(date_time_params[0].strip)
132
+ date_time.to_s.format_date_time("#{date_time_params[1].strip}")
133
+ else
134
+ if Faker.constants.include?(parameter[0].to_sym)
90
135
  eval("Faker::#{parameter[0]}.#{parameter[1]}")
91
136
  else
92
137
  eval(test_value[1])
93
138
  end
94
- end
139
+ end
95
140
  result.to_s
96
141
  end
97
142
  end
98
- # :nocov:
99
143
  end
100
144
 
145
+
146
+ # perform conversion of String to Boolean when reading data from .csv or .xml files
147
+ class ToBoolean
148
+ def self.convert(value)
149
+ value.to_bool unless value.nil?
150
+ end
151
+ end
152
+
153
+
154
+ # perform conversion of Integer to String when reading data from .csv or .xml files
155
+ class ToString
156
+ def self.convert(value)
157
+ if value.is_a? Integer
158
+ value.to_s
159
+ else
160
+ value
161
+ end
162
+ end
163
+ end
164
+
165
+
166
+ # perform conversion of String to Integer when reading data from .csv or .xml files
167
+ class ToInteger
168
+ def self.convert(value)
169
+ if value.is_a? Integer
170
+ value
171
+ else
172
+ if value.is_int?
173
+ value.to_i
174
+ else
175
+ value
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+
182
+ # perform conversion of String to Float when reading data from .csv or .xml files
183
+ class ToFloat
184
+ def self.convert(value)
185
+ if value.is_a? Float
186
+ value
187
+ else
188
+ if value.is_float?
189
+ value.to_f
190
+ else
191
+ value
192
+ end
193
+ end
194
+ end
195
+ end
@@ -23,8 +23,7 @@ module TestCentricity
23
23
  read('Environments', environ_name)
24
24
  when :json
25
25
  # read generic test data from data.json file
26
- raw_data = File.read(JSON_PRIMARY_DATA_FILE)
27
- @generic_data = JSON.parse(raw_data)
26
+ @generic_data = JSON.parse(File.read(JSON_PRIMARY_DATA_FILE))
28
27
  # read environment specific test data
29
28
  data_file = "#{PRIMARY_DATA_PATH}#{environ_name}_data.json"
30
29
  @environ_specific_data = if File.exist?(data_file)
@@ -42,27 +41,21 @@ module TestCentricity
42
41
  Environ.current = @current
43
42
  end
44
43
 
45
- def self.read(key_name, node_name)
46
- if @environ_specific_data.key?(key_name) && @environ_specific_data[key_name].key?(node_name)
47
- node_data = @environ_specific_data[key_name][node_name]
48
- else
49
- raise "No key named #{key_name} in generic and environment-specific data" unless @generic_data.key?(key_name)
50
- raise "No node named #{node_name} in #{key_name} section of generic and environment-specific data" unless @generic_data[key_name].key?(node_name)
51
-
52
- node_data = @generic_data[key_name][node_name]
53
- end
44
+ def self.read(key_name, node_name, options = nil)
45
+ node_data = if @environ_specific_data.key?(key_name) && @environ_specific_data[key_name].key?(node_name)
46
+ @environ_specific_data[key_name][node_name]
47
+ else
48
+ raise "No key named #{key_name} in generic and environment-specific data" unless @generic_data.key?(key_name)
49
+ raise "No node named #{node_name} in #{key_name} section of generic and environment-specific data" unless @generic_data[key_name].key?(node_name)
54
50
 
55
- if node_data.is_a?(Hash)
56
- node_data.each do |key, value|
57
- node_data[key] = calculate_dynamic_value(value) if value.to_s.start_with?('eval!')
58
- end
59
- end
60
- node_data
51
+ @generic_data[key_name][node_name]
52
+ end
53
+ process_data(node_data, options)
61
54
  end
62
55
  end
63
56
 
64
57
 
65
- class Environ < TestCentricity::DataObject
58
+ class Environ
66
59
  @session_id = Time.now.strftime('%d%H%M%S%L')
67
60
  @session_time_stamp = Time.now.strftime('%Y%m%d%H%M%S')
68
61
  @test_environment = ENV['TEST_ENVIRONMENT']
@@ -71,6 +64,7 @@ module TestCentricity
71
64
  @language = ENV['LANGUAGE'] || 'English'
72
65
  @screen_shots = []
73
66
 
67
+ attr_accessor :current
74
68
  attr_accessor :test_environment
75
69
  attr_accessor :session_state
76
70
  attr_accessor :session_code
@@ -140,8 +134,14 @@ module TestCentricity
140
134
  @android_app_id = data['ANDROID_APP_ID']
141
135
  @android_test_id = data['ANDROID_TEST_ID']
142
136
  @deep_link_prefix = data['DEEP_LINK_PREFIX']
137
+ end
138
+
139
+ def self.current
140
+ @current
141
+ end
143
142
 
144
- super
143
+ def self.current=(current)
144
+ @current = current
145
145
  end
146
146
 
147
147
  def self.new_app_session
@@ -1,3 +1,3 @@
1
1
  module TestCentricityMobile
2
- VERSION = '4.1.7'
2
+ VERSION = '4.1.8'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: testcentricity_mobile
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.7
4
+ version: 4.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - A.J. Mrozinski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-11-24 00:00:00.000000000 Z
11
+ date: 2026-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 10.1.1
33
+ version: 10.2.0
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 10.1.1
40
+ version: 10.2.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: parallel_tests
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 4.38.0
103
+ version: 4.39.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 4.38.0
110
+ version: 4.39.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +136,34 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.9.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: activesupport
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '4.0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '4.0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: appium_lib
141
155
  requirement: !ruby/object:Gem::Requirement
142
156
  requirements:
143
157
  - - "~>"
144
158
  - !ruby/object:Gem::Version
145
- version: 16.1.0
159
+ version: 16.1.1
146
160
  type: :runtime
147
161
  prerelease: false
148
162
  version_requirements: !ruby/object:Gem::Requirement
149
163
  requirements:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
- version: 16.1.0
166
+ version: 16.1.1
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: childprocess
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -220,6 +234,20 @@ dependencies:
220
234
  - - ">="
221
235
  - !ruby/object:Gem::Version
222
236
  version: '0'
237
+ - !ruby/object:Gem::Dependency
238
+ name: smarter_csv
239
+ requirement: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ type: :runtime
245
+ prerelease: false
246
+ version_requirements: !ruby/object:Gem::Requirement
247
+ requirements:
248
+ - - ">="
249
+ - !ruby/object:Gem::Version
250
+ version: '0'
223
251
  - !ruby/object:Gem::Dependency
224
252
  name: test-unit
225
253
  requirement: !ruby/object:Gem::Requirement