appom 1.3.3 → 2.0.0
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/README.md +170 -42
- data/lib/appom/configuration.rb +490 -0
- data/lib/appom/element_cache.rb +372 -0
- data/lib/appom/element_container.rb +257 -244
- data/lib/appom/element_finder.rb +142 -121
- data/lib/appom/element_state.rb +458 -0
- data/lib/appom/element_validation.rb +138 -0
- data/lib/appom/exceptions.rb +130 -0
- data/lib/appom/helpers.rb +328 -0
- data/lib/appom/logging.rb +106 -0
- data/lib/appom/page.rb +19 -10
- data/lib/appom/performance.rb +394 -0
- data/lib/appom/retry.rb +178 -0
- data/lib/appom/screenshot.rb +371 -0
- data/lib/appom/section.rb +24 -21
- data/lib/appom/smart_wait.rb +455 -0
- data/lib/appom/version.rb +4 -1
- data/lib/appom/visual.rb +600 -0
- data/lib/appom/wait.rb +96 -33
- data/lib/appom.rb +191 -31
- metadata +35 -19
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require 'erb'
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
# Configuration management for Appom automation framework
|
|
8
|
+
# Handles loading and parsing of YAML configuration files with environment support
|
|
9
|
+
module Appom::Configuration
|
|
10
|
+
# Configuration management with environment-specific settings
|
|
11
|
+
class Config
|
|
12
|
+
include Appom::Logging
|
|
13
|
+
|
|
14
|
+
DEFAULT_CONFIG_FILE = 'appom.yml'
|
|
15
|
+
DEFAULT_CONFIG_PATHS = [
|
|
16
|
+
'./config/appom.yml',
|
|
17
|
+
'./appom.yml',
|
|
18
|
+
'./test/appom.yml',
|
|
19
|
+
'./spec/appom.yml',
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
attr_reader :data, :environment, :config_file
|
|
23
|
+
|
|
24
|
+
def initialize(config_file: nil, environment: nil)
|
|
25
|
+
@config_file = config_file || find_config_file
|
|
26
|
+
@environment = environment || detect_environment
|
|
27
|
+
@data = {}
|
|
28
|
+
load_configuration
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get configuration value with dot notation
|
|
32
|
+
#
|
|
33
|
+
# @param key_path [String, Symbol] The configuration key path using dot notation
|
|
34
|
+
# @param default [Object] Default value to return if key is not found
|
|
35
|
+
# @return [Object] The configuration value or default
|
|
36
|
+
#
|
|
37
|
+
# @example Get a nested value
|
|
38
|
+
# config.get('appom.max_wait_time', 30)
|
|
39
|
+
# config.get('appium.server_url')
|
|
40
|
+
def get(key_path, default = nil)
|
|
41
|
+
keys = key_path.to_s.split('.')
|
|
42
|
+
value = keys.reduce(@data) { |hash, key| hash&.dig(key) }
|
|
43
|
+
value.nil? ? default : value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Set configuration value with dot notation
|
|
47
|
+
#
|
|
48
|
+
# @param key_path [String, Symbol] The configuration key path using dot notation
|
|
49
|
+
# @param value [Object] The value to set
|
|
50
|
+
# @return [Object] The value that was set
|
|
51
|
+
#
|
|
52
|
+
# @example Set a nested value
|
|
53
|
+
# config.set('appom.max_wait_time', 45)
|
|
54
|
+
# config.set('custom.setting', 'value')
|
|
55
|
+
def set(key_path, value)
|
|
56
|
+
keys = key_path.to_s.split('.')
|
|
57
|
+
last_key = keys.pop
|
|
58
|
+
|
|
59
|
+
target = keys.reduce(@data) do |hash, key|
|
|
60
|
+
hash[key] ||= {}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
target[last_key] = value
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Merge configuration hash
|
|
67
|
+
#
|
|
68
|
+
# @param other_config [Hash] Configuration hash to merge
|
|
69
|
+
# @return [Config] Self for method chaining
|
|
70
|
+
#
|
|
71
|
+
# @example Merge additional configuration
|
|
72
|
+
# config.merge!('appom' => { 'log_level' => 'debug' })
|
|
73
|
+
def merge!(other_config)
|
|
74
|
+
@data = deep_merge(@data, other_config)
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Reload configuration from file
|
|
79
|
+
#
|
|
80
|
+
# @return [Config] Self for method chaining
|
|
81
|
+
#
|
|
82
|
+
# @example Reload configuration after file changes
|
|
83
|
+
# config.reload!
|
|
84
|
+
def reload!
|
|
85
|
+
reload_configuration
|
|
86
|
+
log_info("Configuration reloaded from #{@config_file}")
|
|
87
|
+
self
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Validate configuration against schema
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean] True if validation passes
|
|
93
|
+
# @raise [ConfigurationError] If validation fails
|
|
94
|
+
#
|
|
95
|
+
# @example Validate current configuration
|
|
96
|
+
# config.validate!
|
|
97
|
+
def validate! # rubocop:disable Naming/PredicateMethod
|
|
98
|
+
schema = ConfigSchema.new
|
|
99
|
+
errors = schema.validate(@data)
|
|
100
|
+
|
|
101
|
+
if errors.any?
|
|
102
|
+
raise Appom::ConfigurationError.new('validation', @data,
|
|
103
|
+
"Configuration validation failed: #{errors.join(', ')}",)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
log_info('Configuration validation passed')
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Save current configuration to file
|
|
111
|
+
#
|
|
112
|
+
# @param file_path [String, nil] Optional file path to save to, uses current config file if nil
|
|
113
|
+
# @return [void]
|
|
114
|
+
#
|
|
115
|
+
# @example Save configuration
|
|
116
|
+
# config.save!
|
|
117
|
+
# config.save!('backup_config.yml')
|
|
118
|
+
def save!(file_path = nil)
|
|
119
|
+
target_file = file_path || @config_file
|
|
120
|
+
|
|
121
|
+
File.write(target_file, YAML.dump(@data))
|
|
122
|
+
log_info("Configuration saved to #{target_file}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get all configuration as hash
|
|
126
|
+
#
|
|
127
|
+
# @return [Hash] Deep copy of configuration data
|
|
128
|
+
#
|
|
129
|
+
# @example Get configuration as hash
|
|
130
|
+
# config_hash = config.to_h
|
|
131
|
+
def to_h
|
|
132
|
+
@data.dup
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if configuration key exists
|
|
136
|
+
#
|
|
137
|
+
# @param key_path [String, Symbol] The configuration key path using dot notation
|
|
138
|
+
# @return [Boolean] True if key exists, false otherwise
|
|
139
|
+
#
|
|
140
|
+
# @example Check if key exists
|
|
141
|
+
# config.key?('appom.max_wait_time') # => true
|
|
142
|
+
# config.key?('missing.key') # => false
|
|
143
|
+
def key?(key_path)
|
|
144
|
+
!get(key_path).nil?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def find_config_file
|
|
150
|
+
DEFAULT_CONFIG_PATHS.find { |path| File.exist?(path) } || DEFAULT_CONFIG_FILE
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def detect_environment
|
|
154
|
+
ENV['APPOM_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def reload_configuration
|
|
158
|
+
@data = {}
|
|
159
|
+
|
|
160
|
+
if File.exist?(@config_file)
|
|
161
|
+
load_from_file(@config_file)
|
|
162
|
+
else
|
|
163
|
+
log_warn("Configuration file not found: #{@config_file}, using defaults")
|
|
164
|
+
load_defaults
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Override with environment variables
|
|
168
|
+
load_from_environment
|
|
169
|
+
|
|
170
|
+
# Apply environment-specific settings
|
|
171
|
+
apply_environment_settings
|
|
172
|
+
|
|
173
|
+
# Don't log the standard message here - let reload! method handle it
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def load_configuration
|
|
177
|
+
@data = {}
|
|
178
|
+
|
|
179
|
+
if File.exist?(@config_file)
|
|
180
|
+
load_from_file(@config_file)
|
|
181
|
+
else
|
|
182
|
+
log_warn("Configuration file not found: #{@config_file}, using defaults")
|
|
183
|
+
load_defaults
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Override with environment variables
|
|
187
|
+
load_from_environment
|
|
188
|
+
|
|
189
|
+
# Apply environment-specific settings
|
|
190
|
+
apply_environment_settings
|
|
191
|
+
|
|
192
|
+
log_info("Configuration loaded for environment: #{@environment}")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def load_from_file(file_path)
|
|
196
|
+
content = File.read(file_path)
|
|
197
|
+
# Process ERB templates
|
|
198
|
+
erb_content = ERB.new(content).result
|
|
199
|
+
yaml_data = YAML.safe_load(erb_content, aliases: true) || {}
|
|
200
|
+
|
|
201
|
+
@data = deep_merge(@data, yaml_data)
|
|
202
|
+
rescue StandardError => e
|
|
203
|
+
log_error("Failed to load configuration from #{file_path}", { error: e.message })
|
|
204
|
+
load_defaults
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def load_defaults
|
|
208
|
+
@data = {
|
|
209
|
+
'appium' => {
|
|
210
|
+
'server_url' => 'http://localhost:4723/wd/hub',
|
|
211
|
+
'timeout' => 30,
|
|
212
|
+
'implicit_wait' => 5,
|
|
213
|
+
},
|
|
214
|
+
'appom' => {
|
|
215
|
+
'max_wait_time' => 30,
|
|
216
|
+
'log_level' => 'info',
|
|
217
|
+
'screenshot' => {
|
|
218
|
+
'directory' => 'screenshots',
|
|
219
|
+
'format' => 'png',
|
|
220
|
+
'auto_timestamp' => true,
|
|
221
|
+
'on_failure' => true,
|
|
222
|
+
},
|
|
223
|
+
'cache' => {
|
|
224
|
+
'enabled' => true,
|
|
225
|
+
'max_size' => 50,
|
|
226
|
+
'ttl' => 30,
|
|
227
|
+
},
|
|
228
|
+
'retry' => {
|
|
229
|
+
'max_attempts' => 3,
|
|
230
|
+
'base_delay' => 0.5,
|
|
231
|
+
'backoff_multiplier' => 1.5,
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
'capabilities' => {
|
|
235
|
+
'platformName' => 'iOS',
|
|
236
|
+
'deviceName' => 'iPhone Simulator',
|
|
237
|
+
'automationName' => 'XCUITest',
|
|
238
|
+
},
|
|
239
|
+
}
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def load_from_environment
|
|
243
|
+
# Map environment variables to configuration paths
|
|
244
|
+
env_mappings = {
|
|
245
|
+
'APPIUM_SERVER_URL' => 'appium.server_url',
|
|
246
|
+
'APPIUM_TIMEOUT' => 'appium.timeout',
|
|
247
|
+
'APPOM_MAX_WAIT_TIME' => 'appom.max_wait_time',
|
|
248
|
+
'APPOM_LOG_LEVEL' => 'appom.log_level',
|
|
249
|
+
'APPOM_SCREENSHOT_DIR' => 'appom.screenshot.directory',
|
|
250
|
+
'APPOM_CACHE_ENABLED' => 'appom.cache.enabled',
|
|
251
|
+
'DEVICE_NAME' => 'capabilities.deviceName',
|
|
252
|
+
'PLATFORM_NAME' => 'capabilities.platformName',
|
|
253
|
+
'APP_PATH' => 'capabilities.app',
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
env_mappings.each do |env_var, config_path|
|
|
257
|
+
next unless ENV[env_var]
|
|
258
|
+
|
|
259
|
+
value = parse_env_value(ENV.fetch(env_var, nil))
|
|
260
|
+
set(config_path, value)
|
|
261
|
+
log_debug("Override from ENV[#{env_var}]: #{config_path} = #{value}")
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def apply_environment_settings
|
|
266
|
+
return unless @data[@environment]
|
|
267
|
+
|
|
268
|
+
env_config = @data[@environment]
|
|
269
|
+
@data = deep_merge(@data, env_config)
|
|
270
|
+
log_debug("Applied #{@environment} environment settings")
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def parse_env_value(value)
|
|
274
|
+
# Try to parse as JSON/YAML for complex values
|
|
275
|
+
case value.downcase
|
|
276
|
+
when 'true' then true
|
|
277
|
+
when 'false' then false
|
|
278
|
+
when /^\d+$/ then value.to_i
|
|
279
|
+
when /^\d+\.\d+$/ then value.to_f
|
|
280
|
+
else
|
|
281
|
+
# Try parsing as JSON for arrays/hashes
|
|
282
|
+
begin
|
|
283
|
+
JSON.parse(value)
|
|
284
|
+
rescue JSON::ParserError
|
|
285
|
+
value
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def deep_merge(hash1, hash2)
|
|
291
|
+
hash1.merge(hash2) do |_key, old_val, new_val|
|
|
292
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
293
|
+
deep_merge(old_val, new_val)
|
|
294
|
+
else
|
|
295
|
+
new_val
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Configuration validation schema
|
|
302
|
+
class ConfigSchema
|
|
303
|
+
REQUIRED_KEYS = %w[appom appium].freeze
|
|
304
|
+
|
|
305
|
+
APPOM_SCHEMA = {
|
|
306
|
+
'max_wait_time' => { type: Numeric, min: 1, max: 300 },
|
|
307
|
+
'log_level' => { type: String, values: %w[debug info warn error fatal] },
|
|
308
|
+
'screenshot.directory' => { type: String },
|
|
309
|
+
'screenshot.format' => { type: String, values: %w[png jpg jpeg] },
|
|
310
|
+
'screenshot.auto_timestamp' => { type: [TrueClass, FalseClass] },
|
|
311
|
+
'cache.enabled' => { type: [TrueClass, FalseClass] },
|
|
312
|
+
'cache.max_size' => { type: Integer, min: 1, max: 1000 },
|
|
313
|
+
'cache.ttl' => { type: Numeric, min: 1, max: 3600 },
|
|
314
|
+
}.freeze
|
|
315
|
+
|
|
316
|
+
def validate(config_data)
|
|
317
|
+
errors = []
|
|
318
|
+
|
|
319
|
+
# Check required top-level keys
|
|
320
|
+
REQUIRED_KEYS.each do |key|
|
|
321
|
+
errors << "Missing required configuration section: #{key}" unless config_data.key?(key)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Validate appom configuration
|
|
325
|
+
errors.concat(validate_appom_config(config_data['appom'])) if config_data['appom']
|
|
326
|
+
|
|
327
|
+
errors
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
private
|
|
331
|
+
|
|
332
|
+
def validate_appom_config(appom_config)
|
|
333
|
+
errors = []
|
|
334
|
+
|
|
335
|
+
APPOM_SCHEMA.each do |key_path, constraints|
|
|
336
|
+
value = get_nested_value(appom_config, key_path)
|
|
337
|
+
next if value.nil? && !constraints[:required]
|
|
338
|
+
|
|
339
|
+
errors.concat(validate_value(key_path, value, constraints))
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
errors
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def validate_value(key_path, value, constraints)
|
|
346
|
+
errors = []
|
|
347
|
+
|
|
348
|
+
errors.concat(validate_value_type(key_path, value, constraints))
|
|
349
|
+
errors.concat(validate_value_constraints(key_path, value, constraints))
|
|
350
|
+
|
|
351
|
+
errors
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def validate_value_type(key_path, value, constraints)
|
|
355
|
+
return [] unless constraints[:type]
|
|
356
|
+
|
|
357
|
+
valid_types = Array(constraints[:type])
|
|
358
|
+
return [] if valid_types.any? { |type| value.is_a?(type) }
|
|
359
|
+
|
|
360
|
+
["#{key_path} must be of type #{valid_types.join(' or ')}, got #{value.class}"]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def validate_value_constraints(key_path, value, constraints)
|
|
364
|
+
errors = []
|
|
365
|
+
|
|
366
|
+
errors.concat(validate_allowed_values(key_path, value, constraints))
|
|
367
|
+
errors.concat(validate_min_value(key_path, value, constraints))
|
|
368
|
+
errors.concat(validate_max_value(key_path, value, constraints))
|
|
369
|
+
|
|
370
|
+
errors
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def validate_allowed_values(key_path, value, constraints)
|
|
374
|
+
return [] unless constraints[:values] && !constraints[:values].include?(value)
|
|
375
|
+
|
|
376
|
+
["#{key_path} must be one of #{constraints[:values].join(', ')}, got #{value}"]
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def validate_min_value(key_path, value, constraints)
|
|
380
|
+
return [] unless constraints[:min] && value.respond_to?(:<) && value < constraints[:min]
|
|
381
|
+
|
|
382
|
+
["#{key_path} must be at least #{constraints[:min]}, got #{value}"]
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def validate_max_value(key_path, value, constraints)
|
|
386
|
+
return [] unless constraints[:max] && value.respond_to?(:>) && value > constraints[:max]
|
|
387
|
+
|
|
388
|
+
["#{key_path} must be at most #{constraints[:max]}, got #{value}"]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def get_nested_value(hash, key_path)
|
|
392
|
+
keys = key_path.split('.')
|
|
393
|
+
keys.reduce(hash) { |h, key| h&.dig(key) }
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Global configuration instance
|
|
398
|
+
class << self
|
|
399
|
+
attr_writer :config
|
|
400
|
+
|
|
401
|
+
def config
|
|
402
|
+
@config ||= Config.new
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# Configure Appom with block or hash
|
|
406
|
+
def configure(config_data = nil, &)
|
|
407
|
+
if block_given?
|
|
408
|
+
yield config
|
|
409
|
+
elsif config_data
|
|
410
|
+
config.merge!(config_data)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Apply configuration to Appom
|
|
414
|
+
apply_to_appom
|
|
415
|
+
config
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# Load configuration from file
|
|
419
|
+
#
|
|
420
|
+
# @param file_path [String] Path to configuration file
|
|
421
|
+
# @param environment [String, nil] Optional environment override
|
|
422
|
+
# @return [Config] The loaded configuration instance
|
|
423
|
+
#
|
|
424
|
+
# @example Load configuration from custom file
|
|
425
|
+
# Appom::Configuration.load_from_file('config/custom.yml')
|
|
426
|
+
# Appom::Configuration.load_from_file('config/app.yml', environment: 'staging')
|
|
427
|
+
def load_from_file(file_path, environment: nil)
|
|
428
|
+
@config = Config.new(config_file: file_path, environment: environment)
|
|
429
|
+
apply_to_appom
|
|
430
|
+
@config
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Apply configuration values to Appom modules
|
|
434
|
+
def apply_to_appom
|
|
435
|
+
configure_appom_wait_time
|
|
436
|
+
configure_appom_logging
|
|
437
|
+
configure_appom_caching
|
|
438
|
+
configure_appom_screenshots
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# Module-level convenience methods
|
|
442
|
+
|
|
443
|
+
# Get configuration value using dot notation
|
|
444
|
+
#
|
|
445
|
+
# @param key_path [String, Symbol] The configuration key path
|
|
446
|
+
# @param default [Object] Default value if key not found
|
|
447
|
+
# @return [Object] The configuration value or default
|
|
448
|
+
def get(key_path, default = nil)
|
|
449
|
+
config.get(key_path, default)
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Set configuration value using dot notation
|
|
453
|
+
#
|
|
454
|
+
# @param key_path [String, Symbol] The configuration key path
|
|
455
|
+
# @param value [Object] The value to set
|
|
456
|
+
# @return [Object] The value that was set
|
|
457
|
+
def set(key_path, value)
|
|
458
|
+
config.set(key_path, value)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
private
|
|
462
|
+
|
|
463
|
+
def configure_appom_wait_time
|
|
464
|
+
Appom.max_wait_time = config.get('appom.max_wait_time', 30)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def configure_appom_logging
|
|
468
|
+
log_level = config.get('appom.log_level', 'info').to_sym
|
|
469
|
+
Appom.configure_logging(level: log_level)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def configure_appom_caching
|
|
473
|
+
cache_config = {
|
|
474
|
+
enabled: config.get('appom.cache.enabled', true),
|
|
475
|
+
max_size: config.get('appom.cache.max_size', 50),
|
|
476
|
+
ttl: config.get('appom.cache.ttl', 30),
|
|
477
|
+
}
|
|
478
|
+
Appom.configure_cache(**cache_config)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def configure_appom_screenshots
|
|
482
|
+
screenshot_config = {
|
|
483
|
+
directory: config.get('appom.screenshot.directory', 'screenshots'),
|
|
484
|
+
format: config.get('appom.screenshot.format', 'png').to_sym,
|
|
485
|
+
auto_timestamp: config.get('appom.screenshot.auto_timestamp', true),
|
|
486
|
+
}
|
|
487
|
+
Appom::Screenshot.configure(**screenshot_config) if defined?(Appom::Screenshot)
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
end
|