appom 1.4.0 → 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.
data/lib/appom/wait.rb CHANGED
@@ -1,42 +1,103 @@
1
- module Appom
2
- class Wait
3
- DEFAULT_TIMEOUT = 5
4
- DEFAULT_INTERVAL = 0.25
5
-
6
- ##
7
- # Create a new Wait instance
8
- #
9
- # @param [Hash] opts Options for this instance
10
- # @option opts [Numeric] :timeout (5) Seconds to wait before timing out.
11
- # @option opts [Numeric] :interval (0.25) Seconds to sleep between polls.
12
- #
13
- def initialize(opts = {})
14
- @timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT)
15
- @interval = opts.fetch(:interval, DEFAULT_INTERVAL)
16
- end
1
+ # frozen_string_literal: true
17
2
 
18
- ##
19
- # Wait until the given block returns a true value.
20
- #
21
- # @raise [Error::TimeOutError]
22
- # @return [Object] the result of the block
23
- #
24
- def until
25
- end_time = Time.now + @timeout
26
- error_message = ""
27
-
28
- until Time.now > end_time
29
- begin
30
- result = yield
31
- return result if result
32
- rescue => error
33
- error_message = error.message
34
- end
3
+ # Provides wait functionality with configurable timeout and retry interval.
4
+ #
5
+ # The Wait class is used throughout Appom to wait for conditions to become true,
6
+ # such as waiting for elements to appear or disappear, or for elements to reach
7
+ # a certain state.
8
+ #
9
+ # @example Basic wait usage
10
+ # wait = Appom::Wait.new(timeout: 10, interval: 0.5)
11
+ # result = wait.until { some_condition }
12
+ #
13
+ # @example Wait for element to be displayed
14
+ # wait = Appom::Wait.new(timeout: 5)
15
+ # wait.until { element.displayed? }
16
+ #
17
+ # @since 0.1.0
18
+ # @author Harry.Tran
19
+ class Appom::Wait
20
+ include Appom::Logging
21
+
22
+ # Default timeout in seconds
23
+ DEFAULT_TIMEOUT = 5
24
+ # Default retry interval in seconds
25
+ DEFAULT_INTERVAL = 0.25
26
+
27
+ # @!attribute [r] timeout
28
+ # @return [Numeric] The timeout value in seconds
29
+ # @!attribute [r] interval
30
+ # @return [Numeric] The interval between retries in seconds
31
+ attr_reader :timeout, :interval
32
+
33
+ # Create a new Wait instance
34
+ #
35
+ # @param opts [Hash] Options for this instance
36
+ # @option opts [Numeric] :timeout (5) Seconds to wait before timing out
37
+ # @option opts [Numeric] :interval (0.25) Seconds to sleep between polls
38
+ # @return [Wait] A new Wait instance
39
+ #
40
+ # @example Create wait with custom timeout
41
+ # wait = Appom::Wait.new(timeout: 10, interval: 1.0)
42
+ def initialize(opts = {})
43
+ @timeout = opts.fetch(:timeout, DEFAULT_TIMEOUT)
44
+ @interval = opts.fetch(:interval, DEFAULT_INTERVAL)
45
+ end
35
46
 
36
- sleep @interval
47
+ # Wait until the given block returns a truthy value
48
+ #
49
+ # @yield [] Block to execute repeatedly until it returns true
50
+ # @return [Object] The result of the block when it returns truthy
51
+ # @raise [WaitError] If the timeout is reached before condition is met
52
+ # @raise [AppomError] Re-raises any Appom-specific errors from the block
53
+ #
54
+ # @example Wait for element to appear
55
+ # wait.until { page.find_element(:id, 'button') }
56
+ #
57
+ # @example Wait with exception handling
58
+ # wait.until do
59
+ # element = page.find_element(:id, 'button')
60
+ # element.displayed? && element.enabled?
61
+ # end
62
+ def until(&)
63
+ end_time = Time.now + @timeout
64
+ error_message = ''
65
+ last_error = nil
66
+ start_time = Time.now
67
+
68
+ log_wait_start('custom condition', @timeout)
69
+
70
+ until Time.now > end_time
71
+ begin
72
+ result = yield
73
+ if result
74
+ duration = Time.now - start_time
75
+ log_wait_end('custom condition', duration.round(3), success: true)
76
+ return result
77
+ end
78
+ rescue StandardError => e
79
+ last_error = e
80
+ error_message = e.message
37
81
  end
38
82
 
39
- raise StandardError, error_message
83
+ sleep @interval
84
+ end
85
+
86
+ duration = Time.now - start_time
87
+ log_wait_end('custom condition', duration.round(3), success: false)
88
+
89
+ # Handle exceptions differently based on type
90
+ if last_error.is_a?(StandardError) && last_error.instance_of?(StandardError)
91
+ # Wrap StandardError in WaitError with message included
92
+ condition = "condition (last error: #{error_message})"
93
+ raise Appom::WaitError.new(condition, @timeout)
94
+ elsif last_error
95
+ # Raise specific exceptions directly (ArgumentError, RuntimeError, etc.)
96
+ raise last_error
97
+ else
98
+ # No exceptions, just condition never became true
99
+ condition = error_message.empty? ? 'condition' : error_message
100
+ raise Appom::WaitError.new(condition, @timeout)
40
101
  end
41
102
  end
42
103
  end
data/lib/appom.rb CHANGED
@@ -2,57 +2,228 @@
2
2
 
3
3
  require 'appom/version'
4
4
  require 'appium_lib'
5
- require 'appom/cucumber'
5
+ require 'appom/exceptions'
6
+ require 'appom/logging'
7
+ require 'appom/element_validation'
8
+ require 'appom/retry'
9
+ require 'appom/wait'
10
+ require 'appom/smart_wait'
11
+ require 'appom/element_cache'
12
+ require 'appom/screenshot'
13
+ require 'appom/configuration'
6
14
 
15
+ # The main Appom module provides a comprehensive Page Object Model framework for Appium.
16
+ #
17
+ # Appom gives you a simple, clean and semantic DSL for describing mobile applications.
18
+ # It implements the Page Object Model pattern on top of Appium with enhanced error
19
+ # handling, logging, performance monitoring, visual testing, and element state tracking.
20
+ #
21
+ # @example Basic usage
22
+ # # Register Appium driver
23
+ # Appom.register_driver do
24
+ # Appium::Driver.new(options, false)
25
+ # end
26
+ #
27
+ # # Configure global settings
28
+ # Appom.configure do |config|
29
+ # config.max_wait_time = 30
30
+ # end
31
+ #
32
+ # # Define page objects with enhanced features
33
+ # class LoginPage < Appom::Page
34
+ # element :email, :id, 'email_field'
35
+ # element :password, :id, 'password_field'
36
+ # element :submit, :accessibility_id, 'submit_button'
37
+ #
38
+ # def login_with_monitoring(email, password)
39
+ # Appom::Performance.time_operation('login_process') do
40
+ # self.email.set email
41
+ # self.password.set password
42
+ # self.submit.click
43
+ # end
44
+ # end
45
+ # end
46
+ #
47
+ # @example Visual testing
48
+ # # Perform visual regression test
49
+ # Appom::Visual.regression_test('login_screen')
50
+ #
51
+ # @example Performance monitoring
52
+ # # Get performance statistics
53
+ # stats = Appom::Performance.summary
54
+ # puts "Average operation time: #{stats[:average_operation_time]}s"
55
+ #
56
+ # @see https://github.com/hoangtaiki/appom
57
+ # @author Harry.Tran
7
58
  module Appom
8
- # A item was defined without a selector.
9
- class InvalidElementError < StandardError; end
10
- # A block was passed to the method, which it cannot interpreter.
11
- class UnsupportedBlockError < StandardError; end
59
+ include Appom::Logging
60
+ extend Appom::Logging
12
61
 
13
62
  autoload :ElementContainer, 'appom/element_container'
14
63
  autoload :Page, 'appom/page'
15
64
  autoload :Wait, 'appom/wait'
16
65
  autoload :Section, 'appom/section'
17
66
  autoload :ElementFinder, 'appom/element_finder'
67
+ autoload :Helpers, 'appom/helpers'
68
+ autoload :Retry, 'appom/retry'
69
+ autoload :SmartWait, 'appom/smart_wait'
70
+ autoload :ElementCache, 'appom/element_cache'
71
+ autoload :Screenshot, 'appom/screenshot'
72
+ autoload :Configuration, 'appom/configuration'
73
+ autoload :Performance, 'appom/performance'
74
+ autoload :ElementState, 'appom/element_state'
75
+ autoload :Visual, 'appom/visual'
18
76
 
19
77
  class << self
20
- attr_accessor :driver
21
- attr_accessor :max_wait_time
78
+ attr_accessor :driver, :max_wait_time
22
79
 
23
- # Configure appom
80
+ # Configure appom global settings
81
+ #
82
+ # @yieldparam [self] config The Appom module for configuration
83
+ # @example Configure wait time
84
+ # Appom.configure do |config|
85
+ # config.max_wait_time = 30
86
+ # end
24
87
  def configure
25
88
  yield self
26
89
  end
27
90
 
28
- # Register a new appium driver for Appom.
29
- # @return [Appium::Driver] A appium driver instance
30
- def register_driver
31
- @driver = yield
91
+ # Register a new Appium driver for Appom
92
+ #
93
+ # @yield [] Block that returns an Appium::Driver instance
94
+ # @return [Appium::Driver] The registered driver instance
95
+ # @raise [DriverError] If driver registration fails
96
+ #
97
+ # @example Register iOS driver
98
+ # Appom.register_driver do
99
+ # options = {
100
+ # appium_lib: { server_url: 'http://localhost:4723' },
101
+ # caps: { platformName: 'iOS', deviceName: 'iPhone 13' }
102
+ # }
103
+ # Appium::Driver.new(options, false)
104
+ # end
105
+ def register_driver(&)
106
+ log_info('Registering Appium driver')
107
+
108
+ # Register driver with performance monitoring
109
+ @driver = Performance.time_operation('driver_registration', &)
110
+
32
111
  setup_exit_handler
112
+
113
+ # Initialize element state tracking if enabled
114
+ if Configuration.get('element_state.tracking_enabled', false)
115
+ ElementState.tracker
116
+ log_info('Element state tracking initialized')
117
+ end
118
+
119
+ log_info('Appium driver registered successfully')
120
+ @driver
121
+ rescue StandardError => e
122
+ log_error('Failed to register driver', { error: e.message })
123
+ raise DriverError, "Failed to register driver: #{e.message}"
33
124
  end
34
125
 
35
- # Creates a new global driver and quits the old one if it exists.
126
+ # Start the registered Appium driver
127
+ #
128
+ # @raise [DriverNotInitializedError] If no driver has been registered
129
+ # @raise [DriverOperationError] If driver start fails
36
130
  def start_driver
37
- @driver.start_driver
131
+ raise DriverNotInitializedError unless @driver
132
+
133
+ log_info('Starting Appium driver')
134
+
135
+ # Start driver with performance monitoring
136
+ Performance.time_operation('driver_start') do
137
+ @driver.start_driver
138
+ end
139
+
140
+ log_info('Appium driver started successfully')
141
+ rescue DriverNotInitializedError
142
+ raise
143
+ rescue StandardError => e
144
+ log_error('Failed to start driver', { error: e.message })
145
+ raise DriverOperationError.new('start_driver', e.message)
38
146
  end
39
147
 
40
- # Reset the device, relaunching the application.
148
+ # Reset the device, relaunching the application
149
+ #
150
+ # @raise [DriverNotInitializedError] If no driver has been registered
151
+ # @raise [DriverOperationError] If driver reset fails
41
152
  def reset_driver
42
- @driver.reset
153
+ raise DriverNotInitializedError unless @driver
154
+
155
+ log_info('Resetting Appium driver')
156
+
157
+ # Reset driver with performance monitoring
158
+ Performance.time_operation('driver_reset') do
159
+ @driver.reset
160
+ end
161
+
162
+ log_info('Appium driver reset successfully')
163
+ rescue DriverNotInitializedError
164
+ raise
165
+ rescue StandardError => e
166
+ log_error('Failed to reset driver', { error: e.message })
167
+ raise DriverOperationError.new('reset', e.message)
43
168
  end
44
169
 
45
170
  # After run all scenario and exit we will quit driver to close application under test
46
171
  def setup_exit_handler
47
172
  main = Process.pid
48
173
  at_exit do
49
- @driver.driver_quit if Process.pid == main
174
+ cleanup_on_exit(main)
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ # Extracted method to enable testing
181
+ def cleanup_on_exit(main_pid)
182
+ return unless Process.pid == main_pid
183
+
184
+ begin
185
+ # Export performance metrics before quitting
186
+ if defined?(Performance) && Performance.monitor.metrics.any?
187
+ Performance.export_metrics(format: :json)
188
+ log_info('Performance metrics exported on exit')
189
+ end
190
+
191
+ @driver&.driver_quit
192
+ rescue StandardError => e
193
+ # Log error but don't raise during exit
194
+ warn "Warning: Failed to quit driver during exit: #{e.message}"
50
195
  end
51
196
  end
197
+
198
+ public
199
+
200
+ # Performance monitoring convenience methods
201
+ def performance_stats
202
+ Performance.summary
203
+ end
204
+
205
+ def export_performance_metrics(**)
206
+ Performance.export_metrics(**)
207
+ end
208
+
209
+ # Visual testing convenience methods
210
+ def visual_regression_test(name, **)
211
+ Visual.regression_test(name, **)
212
+ end
213
+
214
+ def generate_visual_report(**)
215
+ Visual.generate_report(**)
216
+ end
217
+
218
+ # Element state tracking convenience methods
219
+ def element_tracking_summary
220
+ ElementState.tracking_summary
221
+ end
222
+
223
+ def export_element_tracking(**)
224
+ ElementState.export_data(**)
225
+ end
52
226
  end
53
227
 
54
228
  @max_wait_time = 20
55
229
  end
56
-
57
- World(Appom)
58
-
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appom
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harry.Tran
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-14 00:00:00.000000000 Z
11
+ date: 2026-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appium_lib
@@ -16,46 +16,48 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '9.4'
19
+ version: '16.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '9.4'
26
+ version: '16.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: cucumber
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.3'
33
+ version: '9.0'
34
34
  type: :runtime
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: '2.3'
40
+ version: '9.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop
42
+ name: selenium-webdriver
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0.58'
48
- type: :development
47
+ version: '4.0'
48
+ type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0.58'
55
- description: Appom gives you a simple, clean and semantic for describing your application.
56
- Appom implements the Page Object Model pattern on top of Appium.
54
+ version: '4.0'
55
+ description: Appom provides a clean, semantic DSL for mobile application testing.
56
+ Built on Appium, it includes performance monitoring, visual regression testing,
57
+ element state tracking, smart waiting, and intelligent caching for enterprise-grade
58
+ test automation.
57
59
  email:
58
- - hoang@platphormcorp.com
60
+ - duchoang.vp@gmail.com
59
61
  executables: []
60
62
  extensions: []
61
63
  extra_rdoc_files: []
@@ -63,17 +65,30 @@ files:
63
65
  - LICENSE.txt
64
66
  - README.md
65
67
  - lib/appom.rb
68
+ - lib/appom/configuration.rb
66
69
  - lib/appom/cucumber.rb
70
+ - lib/appom/element_cache.rb
67
71
  - lib/appom/element_container.rb
68
72
  - lib/appom/element_finder.rb
73
+ - lib/appom/element_state.rb
74
+ - lib/appom/element_validation.rb
75
+ - lib/appom/exceptions.rb
76
+ - lib/appom/helpers.rb
77
+ - lib/appom/logging.rb
69
78
  - lib/appom/page.rb
79
+ - lib/appom/performance.rb
80
+ - lib/appom/retry.rb
81
+ - lib/appom/screenshot.rb
70
82
  - lib/appom/section.rb
83
+ - lib/appom/smart_wait.rb
71
84
  - lib/appom/version.rb
85
+ - lib/appom/visual.rb
72
86
  - lib/appom/wait.rb
73
87
  homepage: https://github.com/hoangtaiki/appom
74
88
  licenses:
75
89
  - MIT
76
- metadata: {}
90
+ metadata:
91
+ rubygems_mfa_required: 'true'
77
92
  post_install_message:
78
93
  rdoc_options: []
79
94
  require_paths:
@@ -82,15 +97,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
97
  requirements:
83
98
  - - ">="
84
99
  - !ruby/object:Gem::Version
85
- version: 2.2.3
100
+ version: 3.2.2
86
101
  required_rubygems_version: !ruby/object:Gem::Requirement
87
102
  requirements:
88
103
  - - ">="
89
104
  - !ruby/object:Gem::Version
90
105
  version: '0'
91
106
  requirements: []
92
- rubygems_version: 3.2.19
107
+ rubygems_version: 3.4.10
93
108
  signing_key:
94
109
  specification_version: 4
95
- summary: A Page Object Model for Appium
110
+ summary: A comprehensive Page Object Model framework for Appium with advanced testing
111
+ capabilities
96
112
  test_files: []