xcmonkey 1.1.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d3bd605b57ec05a83a3cca39707b5c2fc9529828131b9ac872f3dc478180bbb
4
- data.tar.gz: 8793e64f73775a2c8de84d1b79d0aebd258df7c22b9c8f43de2618f63f306d4e
3
+ metadata.gz: be0e0b66cbc58704452a6defce61d6bd9f7ba88f7489796f4b7cc6a4f4489a11
4
+ data.tar.gz: 8551a6fd090f89cf20c2cd2e81bc8edc4856b9eb5516a50227db772cb0f013eb
5
5
  SHA512:
6
- metadata.gz: 4e4634518e71dac0ed206d4fec26fc707fb9dbea8116c87db1959695261e8ee311246db203317790433d18b5e5250074f1f17bf567101dfec6767a1ed5db0670
7
- data.tar.gz: 0e696e9804b94e6106f203b9bcda008f57dbe7fbda78a68ff7d57a8cfa980e4b691b533f430bac855ae8ed56ae0e7ac5085f305853bae8f03652095871287a94
6
+ metadata.gz: 5f3e1fe8bb0bccfd056c9f7087c32c2cd06adee0d0dca6a7b7ba6b8e038130e8d0f991c1e3bcb8d4b7c6d1c9ad63a5ae1fa718e979baaa8ff8f41ba98ae03b56
7
+ data.tar.gz: 1aabd404928c4cbc3e9d7a3fbe0c9f88ca155c25f745eb7e708cca7330a340740908343af231bc9fd9e5759d380f31816a920a53c40a6be6dbfa1916b50363db
data/.github/stale.yml ADDED
@@ -0,0 +1,18 @@
1
+ # Number of days of inactivity before an issue becomes stale
2
+ daysUntilStale: 14
3
+ # Number of days of inactivity before a stale issue is closed
4
+ daysUntilClose: 7
5
+ # Issues with these labels will never be considered stale
6
+ exemptLabels:
7
+ - important
8
+ # Label to use when marking an issue as stale
9
+ staleLabel: stale
10
+ # Comment to post when marking an issue as stale. Set to `false` to disable
11
+ markComment: >
12
+ This issue has been automatically marked as stale because it has not had
13
+ recent activity. It will be closed if no further activity occurs. Thank you
14
+ for your contributions.
15
+ # Comment to post when closing a stale issue. Set to `false` to disable
16
+ closeComment: >
17
+ This issue has been automatically closed due to inactivity.
18
+ Please open a new issue if it's still valid.
data/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ # xcmonkey changelog
2
+
3
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4
+
5
+ ## [1.3.0](https://github.com/alteral/xcmonkey/releases/tag/1.3.0)
6
+
7
+ _January 29, 2023_
8
+
9
+ ### 🔄 Changed
10
+
11
+ - `duration` option is renamed to `event-count`
12
+
13
+ ### ✅ Added
14
+
15
+ - New test options that allow to
16
+ - specify gestures to exclude from the test
17
+ - set up throttle between events
18
+ - ignore app crashes
19
+
20
+ ## [1.2.0](https://github.com/alteral/xcmonkey/releases/tag/1.2.0)
21
+
22
+ _January 23, 2023_
23
+
24
+ ### ✅ Added
25
+
26
+ - Force keep xcmonkey in the target app
27
+
28
+ ## [1.1.0](https://github.com/alteral/xcmonkey/releases/tag/1.1.0)
29
+
30
+ _January 20, 2023_
31
+
32
+ ### ✅ Added
33
+
34
+ - Support [fastlane](https://github.com/fastlane/fastlane) integration
35
+
36
+ ## [1.0.0](https://github.com/alteral/xcmonkey/releases/tag/1.0.0)
37
+
38
+ _January 09, 2023_
39
+
40
+ ### ✅ Added
41
+
42
+ - xcmonkey `test`, `repeat` and `describe` commands
data/Gemfile CHANGED
@@ -4,3 +4,17 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in xcmonkey.gemspec
6
6
  gemspec
7
+
8
+ gem 'bundler'
9
+ gem 'fasterer', '0.9.0'
10
+ gem 'fastlane'
11
+ gem 'rake'
12
+ gem 'rspec'
13
+ gem 'rspec_junit_formatter'
14
+ gem 'rubocop', '1.44.0'
15
+ gem 'rubocop-performance'
16
+ gem 'rubocop-rake', '0.6.0'
17
+ gem 'rubocop-require_tools'
18
+ gem 'rubocop-rspec', '2.15.0'
19
+ gem 'simplecov'
20
+ gem 'solargraph'
data/README.md CHANGED
@@ -39,7 +39,7 @@ gem 'xcmonkey'
39
39
  ### To run a stress test
40
40
 
41
41
  ```bash
42
- xcmonkey test --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA" --bundle-id "com.apple.Maps" --duration 100
42
+ $ xcmonkey test --event-count 100 --bundle-id "com.apple.Maps" --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA"
43
43
 
44
44
  12:44:19.343: Device info: {
45
45
  "name": "iPhone 14 Pro",
@@ -97,6 +97,26 @@ xcmonkey repeat --session-path "./xcmonkey-session.json"
97
97
  xcmonkey describe -x 20 -y 625 --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA"
98
98
  ```
99
99
 
100
+ ### Test options reference
101
+
102
+ The table below lists all options you can include on the `xcmonkey test` command line.
103
+
104
+ | Category | Option | Description | Default |
105
+ | --- | --- | --- | --- |
106
+ | **General** | `-h, --help` | Display help documentation | |
107
+ | | `-v, --version` | Display version information | |
108
+ | | `-t, --trace` | Display backtrace when an error occurs | |
109
+ | **Events** | `-u, --udid <string>` | Set device UDID | |
110
+ | | `-b, --bundle-id <string>` | Set target bundle identifier | |
111
+ | | `-s, --session-path <string>` | Path where test session should be saved | |
112
+ | | `-e, --event-count <integer>` | Set events count | `60` |
113
+ | | `--exclude-taps` | Exclude taps from gestures list | `false` |
114
+ | | `--exclude-swipes` | Exclude swipes from gestures list | `false` |
115
+ | | `--exclude-presses` | Exclude presses from gestures list | `false` |
116
+ | | `--disable-simulator-keyboard` | Should simulator keyboard be disable? | `false` |
117
+ | **Debugging** | `--ignore-crashes` | Should app crashes be ignored? | `false` |
118
+ | | `--throttle <milliseconds>` | Fixed delay between events | `0` |
119
+
100
120
  ## [fastlane](https://github.com/fastlane/fastlane) integration
101
121
 
102
122
  To run *xcmonkey* from *fastlane*, add the following code to your `Fastfile`:
@@ -106,9 +126,9 @@ require 'xcmonkey'
106
126
 
107
127
  lane :test do
108
128
  Xcmonkey.new(
109
- udid: '413EA256-CFFB-4312-94A6-12592BEE4CBA',
129
+ event_count: 100,
110
130
  bundle_id: 'com.apple.Maps',
111
- duration: 100
131
+ udid: '413EA256-CFFB-4312-94A6-12592BEE4CBA'
112
132
  ).run
113
133
  end
114
134
  ```
data/bin/xcmonkey CHANGED
@@ -17,16 +17,26 @@ class Xcmonkey
17
17
  c.description = 'Runs monkey test'
18
18
  c.option('-u', '--udid STRING', String, 'Set device UDID')
19
19
  c.option('-b', '--bundle-id STRING', String, 'Set target bundle identifier')
20
- c.option('-d', '--duration SECONDS', Integer, 'Test duration in seconds. Defaults to `60`')
21
- c.option('-k', '--enable-simulator-keyboard', 'Should simulator keyboard be enabled? Defaults to `true`')
22
- c.option('-s', '--session-path STRING', String, 'Path where monkey testing session should be saved. Defaults to current directory')
20
+ c.option('-e', '--event-count NUMBER', Integer, 'Set events count. Defaults to `60`')
21
+ c.option('-s', '--session-path STRING', String, 'Path where test session should be saved')
22
+ c.option('--throttle MILLISECONDS', Integer, 'Fixed delay between events in milliseconds. Defaults to `0`')
23
+ c.option('--exclude-taps', 'Exclude taps from gestures list. Defaults to `false`')
24
+ c.option('--exclude-swipes', 'Exclude swipes from gestures list. Defaults to `false`')
25
+ c.option('--exclude-presses', 'Exclude presses from gestures list. Defaults to `false`')
26
+ c.option('--ignore-crashes', 'Should app crashes be ignored? Defaults to `false`')
27
+ c.option('--disable-simulator-keyboard', 'Should simulator keyboard be disable? Defaults to `false`')
23
28
  c.action do |_, options|
24
29
  params = {
25
30
  udid: options.udid,
26
31
  bundle_id: options.bundle_id,
27
- duration: options.duration,
32
+ event_count: options.event_count,
33
+ throttle: options.throttle,
28
34
  session_path: options.session_path,
29
- enable_simulator_keyboard: options.enable_simulator_keyboard
35
+ exclude_taps: options.exclude_taps,
36
+ exclude_swipes: options.exclude_swipes,
37
+ exclude_presses: options.exclude_presses,
38
+ ignore_crashes: options.ignore_crashes,
39
+ disable_simulator_keyboard: options.disable_simulator_keyboard
30
40
  }
31
41
  Xcmonkey.new(params).run
32
42
  end
data/fastlane/Fastfile CHANGED
@@ -8,9 +8,9 @@ lane :release do
8
8
  set_github_release(
9
9
  repository_name: 'alteral/xcmonkey',
10
10
  api_token: ENV.fetch("GITHUB_TOKEN", nil),
11
- name: "xcmonkey v#{version}",
12
- tag_name: "v#{version}",
13
- description: "v#{version}",
11
+ name: version,
12
+ tag_name: version,
13
+ description: "See [CHANGELOG.md](https://github.com/alteral/xcmonkey/blob/main/CHANGELOG.md##{version.delete('.')}})",
14
14
  commitish: git_branch,
15
15
  upload_assets: [gem_path]
16
16
  )
@@ -1,12 +1,14 @@
1
1
  class Driver
2
- attr_accessor :udid, :bundle_id, :enable_simulator_keyboard, :session_duration, :session_path, :session_actions
2
+ attr_accessor :udid, :bundle_id, :disable_simulator_keyboard, :event_count, :session_path, :session_actions, :ignore_crashes, :throttle
3
3
 
4
4
  def initialize(params)
5
5
  self.udid = params[:udid]
6
+ self.throttle = params[:throttle]
6
7
  self.bundle_id = params[:bundle_id]
7
- self.session_duration = params[:duration]
8
+ self.event_count = params[:event_count]
8
9
  self.session_path = params[:session_path]
9
- self.enable_simulator_keyboard = params[:enable_simulator_keyboard]
10
+ self.ignore_crashes = params[:ignore_crashes]
11
+ self.disable_simulator_keyboard = params[:disable_simulator_keyboard]
10
12
  self.session_actions = params[:session_actions]
11
13
  @session = { params: params, actions: [] }
12
14
  ensure_driver_installed
@@ -16,16 +18,15 @@ class Driver
16
18
  puts
17
19
  ensure_device_exists
18
20
  ensure_app_installed
19
- terminate_app
20
- open_home_screen(with_tracker: true)
21
- launch_app
21
+ terminate_app(bundle_id)
22
+ launch_app(target_bundle_id: bundle_id, wait_for_state_update: true)
23
+ @running_apps = list_running_apps
22
24
  end
23
25
 
24
26
  def monkey_test(gestures)
25
27
  monkey_test_precondition
26
- app_elements = describe_ui.shuffle
27
- current_time = Time.now
28
- while Time.now < current_time + session_duration
28
+ event_count.times do |counter|
29
+ app_elements = describe_ui.shuffle
29
30
  el1_coordinates = central_coordinates(app_elements.first)
30
31
  el2_coordinates = central_coordinates(app_elements.last)
31
32
  case gestures.sample
@@ -52,18 +53,14 @@ class Driver
52
53
  else
53
54
  next
54
55
  end
55
- app_elements = describe_ui.shuffle
56
- next unless app_elements.include?(@home_tracker)
57
-
58
- save_session
59
- Logger.error('App lost')
56
+ checkup(counter)
60
57
  end
61
58
  save_session
62
59
  end
63
60
 
64
61
  def repeat_monkey_test
65
62
  monkey_test_precondition
66
- session_actions.each do |action|
63
+ session_actions.each_with_index do |action, counter|
67
64
  case action['type']
68
65
  when 'tap'
69
66
  tap(coordinates: { x: action['x'], y: action['y'] })
@@ -78,13 +75,16 @@ class Driver
78
75
  else
79
76
  next
80
77
  end
81
- Logger.error('App lost') if describe_ui.shuffle.include?(@home_tracker)
78
+ checkup(counter)
82
79
  end
83
80
  end
84
81
 
85
- def open_home_screen(with_tracker: false)
86
- `idb ui button --udid #{udid} HOME`
87
- detect_home_unique_element if with_tracker
82
+ def checkup(counter)
83
+ detect_app_state_change
84
+ if counter % 5 == 0 || throttle.to_i > 0 # Track running apps after every 5th action
85
+ track_running_apps # (unless `throttle` was provided) to speed up the test
86
+ end
87
+ check_speed_limit
88
88
  end
89
89
 
90
90
  def describe_ui
@@ -97,13 +97,13 @@ class Driver
97
97
  point_info
98
98
  end
99
99
 
100
- def launch_app
101
- `idb launch --udid #{udid} #{bundle_id}`
102
- wait_until_app_launched
100
+ def launch_app(target_bundle_id:, wait_for_state_update: false)
101
+ `idb launch --udid #{udid} #{target_bundle_id}`
102
+ wait_until_app_launched(target_bundle_id) if wait_for_state_update
103
103
  end
104
104
 
105
- def terminate_app
106
- `idb terminate --udid #{udid} #{bundle_id} 2>/dev/null`
105
+ def terminate_app(target_bundle_id)
106
+ `idb terminate --udid #{udid} #{target_bundle_id} 2>/dev/null`
107
107
  end
108
108
 
109
109
  def boot_simulator
@@ -117,7 +117,7 @@ class Driver
117
117
 
118
118
  def configure_simulator_keyboard
119
119
  shutdown_simulator
120
- keyboard_status = enable_simulator_keyboard ? 0 : 1
120
+ keyboard_status = disable_simulator_keyboard ? 1 : 0
121
121
  `defaults write com.apple.iphonesimulator ConnectHardwareKeyboard #{keyboard_status}`
122
122
  end
123
123
 
@@ -130,6 +130,10 @@ class Driver
130
130
  `idb list-apps --udid #{udid} --json`.split("\n").map! { |app| JSON.parse(app) }
131
131
  end
132
132
 
133
+ def list_running_apps
134
+ list_apps.select { |app| app['process_state'] == 'Running' }
135
+ end
136
+
133
137
  def ensure_app_installed
134
138
  return if list_apps.any? { |app| app['bundle_id'] == bundle_id }
135
139
 
@@ -144,6 +148,9 @@ class Driver
144
148
  if device['type'] == 'simulator'
145
149
  configure_simulator_keyboard
146
150
  boot_simulator
151
+ else
152
+ Logger.error('xcmonkey does not support real devices yet. ' \
153
+ 'For more information see https://github.com/alteral/xcmonkey/issues/7')
147
154
  end
148
155
  end
149
156
 
@@ -160,10 +167,8 @@ class Driver
160
167
  end
161
168
 
162
169
  def swipe(start_coordinates:, end_coordinates:, duration:)
163
- Logger.info(
164
- "Swipe (#{duration}s):",
165
- payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}"
166
- )
170
+ payload = "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}"
171
+ Logger.info("Swipe (#{duration}s):", payload: payload)
167
172
  unless session_actions
168
173
  @session[:actions] << {
169
174
  type: :swipe,
@@ -183,8 +188,8 @@ class Driver
183
188
  x = (frame['x'] + (frame['width'] / 2)).abs.to_i
184
189
  y = (frame['y'] + (frame['height'] / 2)).abs.to_i
185
190
  {
186
- x: x > screen_size[:width].to_i ? rand(0..screen_size[:width].to_i) : x,
187
- y: y > screen_size[:height].to_i ? rand(0..screen_size[:height].to_i) : y
191
+ x: x > screen_size[:width].to_i ? random_coordinates[:x] : x,
192
+ y: y > screen_size[:height].to_i ? random_coordinates[:y] : y
188
193
  }
189
194
  end
190
195
 
@@ -217,31 +222,67 @@ class Driver
217
222
  end
218
223
 
219
224
  def save_session
225
+ return if session_path.nil?
226
+
220
227
  File.write("#{session_path}/xcmonkey-session.json", JSON.pretty_generate(@session))
221
228
  end
222
229
 
223
- private
230
+ # This function takes ≈200ms
231
+ def track_running_apps
232
+ current_list_of_running_apps = list_running_apps
233
+ if @running_apps != current_list_of_running_apps
234
+ currently_running_bundle_ids = current_list_of_running_apps.map { |app| app['bundle_id'] }
235
+ previously_running_bundle_ids = @running_apps.map { |app| app['bundle_id'] }
236
+ new_apps = currently_running_bundle_ids - previously_running_bundle_ids
224
237
 
225
- def ensure_driver_installed
226
- Logger.error("'idb' doesn't seem to be installed") if `which idb`.strip.empty?
238
+ return if new_apps.empty?
239
+
240
+ launch_app(target_bundle_id: bundle_id)
241
+
242
+ new_apps.each do |id|
243
+ Logger.warn("Shutting down: #{id}")
244
+ terminate_app(id)
245
+ end
246
+ end
227
247
  end
228
248
 
229
- def detect_home_unique_element
230
- @home_tracker ||= describe_ui.reverse.detect do |el|
231
- sleep(1)
232
- !el['AXUniqueId'].nil? && !el['AXUniqueId'].empty? && el['type'] == 'Button'
249
+ # This function takes ≈300ms
250
+ def detect_app_state_change
251
+ return unless detect_app_in_background
252
+
253
+ target_app_is_running = list_running_apps.any? { |app| app['bundle_id'] == bundle_id }
254
+
255
+ if target_app_is_running || ignore_crashes
256
+ launch_app(target_bundle_id: bundle_id)
257
+ else
258
+ save_session
259
+ Logger.error("Target app has crashed or been terminated")
233
260
  end
234
- @home_tracker
235
261
  end
236
262
 
237
- def wait_until_app_launched
263
+ def detect_app_in_background
264
+ current_app_label = describe_ui.detect { |el| el['type'] == 'Application' }['AXLabel']
265
+ current_app_label.nil? || current_app_label.strip.empty?
266
+ end
267
+
268
+ def check_speed_limit
269
+ sleep(throttle / 1000.0) if throttle.to_i > 0
270
+ end
271
+
272
+ private
273
+
274
+ def ensure_driver_installed
275
+ Logger.error("'idb' doesn't seem to be installed") if `which idb`.strip.empty?
276
+ end
277
+
278
+ def wait_until_app_launched(target_bundle_id)
238
279
  app_is_running = false
239
280
  current_time = Time.now
240
281
  while !app_is_running && Time.now < current_time + 5
241
- app_info = list_apps.detect { |app| app['bundle_id'] == bundle_id }
282
+ app_info = list_apps.detect { |app| app['bundle_id'] == target_bundle_id }
242
283
  app_is_running = app_info && app_info['process_state'] == 'Running'
243
284
  end
244
- Logger.error("Can't run the app #{bundle_id}") unless app_is_running
285
+ Logger.error("Can't run the app #{target_bundle_id}") unless app_is_running
245
286
  Logger.info('App info:', payload: JSON.pretty_generate(app_info))
246
287
  end
247
288
  end
@@ -1,5 +1,5 @@
1
1
  class Repeater
2
- attr_accessor :udid, :bundle_id, :enable_simulator_keyboard, :actions
2
+ attr_accessor :udid, :bundle_id, :disable_simulator_keyboard, :ignore_crashes, :actions, :throttle
3
3
 
4
4
  def initialize(params)
5
5
  validate_session(params[:session_path])
@@ -8,8 +8,10 @@ class Repeater
8
8
  def run
9
9
  params = {
10
10
  udid: udid,
11
+ throttle: throttle,
11
12
  bundle_id: bundle_id,
12
- enable_simulator_keyboard: enable_simulator_keyboard,
13
+ ignore_crashes: ignore_crashes,
14
+ disable_simulator_keyboard: disable_simulator_keyboard,
13
15
  session_actions: actions
14
16
  }
15
17
  Driver.new(params).repeat_monkey_test
@@ -34,6 +36,10 @@ class Repeater
34
36
  self.bundle_id = session['params']['bundle_id']
35
37
  Logger.error('Provided session is not valid: `bundle_id` should not be `nil`') if bundle_id.nil?
36
38
 
37
- self.enable_simulator_keyboard = session['params']['enable_simulator_keyboard']
39
+ self.throttle = session['params']['throttle']
40
+
41
+ self.ignore_crashes = session['params']['ignore_crashes']
42
+
43
+ self.disable_simulator_keyboard = session['params']['disable_simulator_keyboard']
38
44
  end
39
45
  end
@@ -1,3 +1,3 @@
1
1
  class Xcmonkey
2
- VERSION = '1.1.0'
2
+ VERSION = '1.3.0'
3
3
  end
data/lib/xcmonkey.rb CHANGED
@@ -7,14 +7,15 @@ require_relative 'xcmonkey/logger'
7
7
  require_relative 'xcmonkey/driver'
8
8
 
9
9
  class Xcmonkey
10
- attr_accessor :driver
10
+ attr_accessor :params, :driver
11
11
 
12
12
  def initialize(params)
13
- params[:session_path] = Dir.pwd if params[:session_path].nil?
14
- params[:duration] = 60 if params[:duration].nil?
15
- params[:enable_simulator_keyboard] = true if params[:enable_simulator_keyboard].nil?
16
- ensure_required_params(params)
13
+ params[:event_count] = 60 if params[:event_count].nil?
14
+ params[:ignore_crashes] = false if params[:ignore_crashes].nil?
15
+ params[:disable_simulator_keyboard] = false if params[:disable_simulator_keyboard].nil?
16
+ self.params = params
17
17
  self.driver = Driver.new(params)
18
+ ensure_required_params
18
19
  end
19
20
 
20
21
  def run
@@ -22,21 +23,21 @@ class Xcmonkey
22
23
  end
23
24
 
24
25
  def gestures
25
- taps = [:precise_tap, :blind_tap] * 10
26
- swipes = [:precise_swipe, :blind_swipe] * 5
27
- presses = [:precise_press, :blind_press]
26
+ taps = params[:exclude_taps] ? [] : [:precise_tap, :blind_tap] * 10
27
+ swipes = params[:exclude_swipes] ? [] : [:precise_swipe, :blind_swipe] * 5
28
+ presses = params[:exclude_presses] ? [] : [:precise_press, :blind_press]
28
29
  taps + swipes + presses
29
30
  end
30
31
 
31
- def ensure_required_params(params)
32
+ def ensure_required_params
32
33
  Logger.error('UDID should be provided') if params[:udid].nil?
33
34
 
34
35
  Logger.error('Bundle identifier should be provided') if params[:bundle_id].nil?
35
36
 
36
- Logger.error('Session path should be a directory') if params[:session_path].nil? || !File.directory?(params[:session_path])
37
+ Logger.error('Session path should be a directory') if params[:session_path] && !File.directory?(params[:session_path])
37
38
 
38
- if params[:duration].nil? || !params[:duration].kind_of?(Integer) || !params[:duration].positive?
39
- Logger.error('Duration must be Integer and not less than 1 second')
39
+ if params[:event_count].nil? || !params[:event_count].kind_of?(Integer) || !params[:event_count].positive?
40
+ Logger.error('Event count must be Integer and not less than 1')
40
41
  end
41
42
  end
42
43
  end
@@ -2,15 +2,17 @@ describe Describer do
2
2
  let(:udid) { `xcrun simctl list | grep " iPhone 14 Pro Max"`.split("\n")[0].split('(')[1].split(')')[0] }
3
3
  let(:driver) { Driver.new(udid: udid) }
4
4
 
5
- it 'verifies that point can be described (integer)' do
5
+ before do
6
6
  allow(Logger).to receive(:info)
7
+ end
8
+
9
+ it 'verifies that point can be described (integer)' do
7
10
  driver.boot_simulator
8
11
  point_info = described_class.new(udid: udid, x: 10, y: 10).run
9
12
  expect(point_info).not_to be_empty
10
13
  end
11
14
 
12
15
  it 'verifies that point can be described (string)' do
13
- allow(Logger).to receive(:info)
14
16
  driver.boot_simulator
15
17
  point_info = described_class.new(udid: udid, x: '10', y: '10').run
16
18
  expect(point_info).not_to be_empty
data/spec/driver_spec.rb CHANGED
@@ -1,9 +1,13 @@
1
1
  describe Driver do
2
2
  let(:udid) { `xcrun simctl list | grep " iPhone 14 Pro Max"`.split("\n")[0].split('(')[1].split(')')[0] }
3
3
  let(:bundle_id) { 'com.apple.Maps' }
4
- let(:driver) { described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd) }
4
+ let(:driver) { described_class.new(udid: udid, bundle_id: bundle_id) }
5
5
  let(:driver_with_session) { described_class.new(udid: udid, bundle_id: bundle_id, session_actions: [{ type: 'tap', x: 0, y: 0 }]) }
6
6
 
7
+ before do
8
+ allow(Logger).to receive(:info)
9
+ end
10
+
7
11
  it 'verifies that sumulator was booted' do
8
12
  error_message = "Failed to boot #{udid}"
9
13
  expect(Logger).not_to receive(:error).with(error_message, payload: nil)
@@ -16,18 +20,6 @@ describe Driver do
16
20
  expect(ui).not_to be_empty
17
21
  end
18
22
 
19
- it 'verifies that home screen can be opened' do
20
- driver.boot_simulator
21
- home_tracker = driver.open_home_screen(with_tracker: true)
22
- expect(home_tracker).not_to be_empty
23
- end
24
-
25
- it 'verifies that home screen can be opened without tracker' do
26
- driver.boot_simulator
27
- home_tracker = driver.open_home_screen(with_tracker: false)
28
- expect(home_tracker).to be_nil
29
- end
30
-
31
23
  it 'verifies that list of targets can be showed' do
32
24
  list_targets = driver.list_targets
33
25
  expect(list_targets).not_to be_empty
@@ -56,9 +48,7 @@ describe Driver do
56
48
  end
57
49
 
58
50
  it 'verifies that device exists' do
59
- payload = driver.list_targets.detect { |target| target['udid'] == udid }
60
51
  expect(Logger).not_to receive(:error)
61
- expect(Logger).to receive(:info).with('Device info:', payload: JSON.pretty_generate(payload))
62
52
  expect(driver).to receive(:boot_simulator)
63
53
  expect(driver).to receive(:configure_simulator_keyboard)
64
54
  expect { driver.ensure_device_exists }.not_to raise_error
@@ -119,7 +109,7 @@ describe Driver do
119
109
 
120
110
  it 'verifies that simulator keyboard can be enabled' do
121
111
  allow(driver).to receive(:is_simulator_keyboard_enabled?).and_return(false)
122
- driver = described_class.new(udid: udid, bundle_id: bundle_id, enable_simulator_keyboard: true)
112
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, disable_simulator_keyboard: false)
123
113
  expect(driver).to receive(:shutdown_simulator)
124
114
  driver.configure_simulator_keyboard
125
115
  keyboard_state = `defaults read com.apple.iphonesimulator`.split("\n").grep(/ConnectHardwareKeyboard/)
@@ -129,7 +119,7 @@ describe Driver do
129
119
 
130
120
  it 'verifies that simulator keyboard can be disabled' do
131
121
  allow(driver).to receive(:is_simulator_keyboard_enabled?).and_return(true)
132
- driver = described_class.new(udid: udid, bundle_id: bundle_id, enable_simulator_keyboard: false)
122
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, disable_simulator_keyboard: true)
133
123
  expect(driver).to receive(:shutdown_simulator)
134
124
  driver.configure_simulator_keyboard
135
125
  keyboard_state = `defaults read com.apple.iphonesimulator`.split("\n").grep(/ConnectHardwareKeyboard/)
@@ -137,18 +127,25 @@ describe Driver do
137
127
  expect(keyboard_state.first).to include('1')
138
128
  end
139
129
 
140
- it 'verifies that app can be launched' do
130
+ it 'verifies that app can be launched with waiting' do
131
+ expect(Logger).not_to receive(:error)
132
+ expect(driver).to receive(:wait_until_app_launched)
133
+ driver.boot_simulator
134
+ driver.terminate_app(bundle_id)
135
+ expect { driver.launch_app(target_bundle_id: bundle_id, wait_for_state_update: true) }.not_to raise_error
136
+ end
137
+
138
+ it 'verifies that app can be launched without waiting' do
141
139
  expect(Logger).not_to receive(:error)
142
- expect(Logger).to receive(:info)
140
+ expect(driver).not_to receive(:wait_until_app_launched)
143
141
  driver.boot_simulator
144
- driver.terminate_app
145
- expect { driver.launch_app }.not_to raise_error
142
+ driver.terminate_app(bundle_id)
143
+ expect { driver.launch_app(target_bundle_id: bundle_id) }.not_to raise_error
146
144
  end
147
145
 
148
146
  it 'verifies tap in new session' do
149
147
  driver.boot_simulator
150
148
  coordinates = { x: 1, y: 1 }
151
- expect(Logger).to receive(:info).with('Tap:', payload: JSON.pretty_generate(coordinates))
152
149
  driver.tap(coordinates: coordinates)
153
150
  expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
154
151
  end
@@ -156,7 +153,6 @@ describe Driver do
156
153
  it 'verifies tap in old session' do
157
154
  driver_with_session.boot_simulator
158
155
  coordinates = { x: 1, y: 1 }
159
- expect(Logger).to receive(:info).with('Tap:', payload: JSON.pretty_generate(coordinates))
160
156
  driver_with_session.tap(coordinates: coordinates)
161
157
  expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
162
158
  end
@@ -165,7 +161,6 @@ describe Driver do
165
161
  driver.boot_simulator
166
162
  duration = 0.5
167
163
  coordinates = { x: 1, y: 1 }
168
- expect(Logger).to receive(:info).with("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
169
164
  driver.press(coordinates: coordinates, duration: duration)
170
165
  expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
171
166
  end
@@ -174,7 +169,6 @@ describe Driver do
174
169
  driver_with_session.boot_simulator
175
170
  duration = 0.5
176
171
  coordinates = { x: 1, y: 1 }
177
- expect(Logger).to receive(:info).with("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
178
172
  driver_with_session.press(coordinates: coordinates, duration: duration)
179
173
  expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
180
174
  end
@@ -184,7 +178,6 @@ describe Driver do
184
178
  duration = 0.5
185
179
  start_coordinates = { x: 1, y: 1 }
186
180
  end_coordinates = { x: 2, y: 2 }
187
- expect(Logger).to receive(:info).with("Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}")
188
181
  driver.swipe(start_coordinates: start_coordinates, end_coordinates: end_coordinates, duration: duration)
189
182
  expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
190
183
  end
@@ -194,28 +187,33 @@ describe Driver do
194
187
  duration = 0.5
195
188
  start_coordinates = { x: 1, y: 1 }
196
189
  end_coordinates = { x: 2, y: 2 }
197
- expect(Logger).to receive(:info).with("Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}")
198
190
  driver_with_session.swipe(start_coordinates: start_coordinates, end_coordinates: end_coordinates, duration: duration)
199
191
  expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
200
192
  end
201
193
 
202
194
  it 'verifies that session can be saved' do
195
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd)
203
196
  expect(File).to receive(:write)
204
197
  driver.instance_variable_set(:@session, { params: {}, actions: [] })
205
198
  driver.save_session
206
199
  end
207
200
 
201
+ it "verifies that session won't be saved if path is not provided" do
202
+ expect(File).not_to receive(:write)
203
+ driver.save_session
204
+ end
205
+
208
206
  it 'verifies that monkey_test_precondition works fine' do
209
207
  driver.monkey_test_precondition
210
208
  app_info = driver.list_apps.detect { |app| app['bundle_id'] == bundle_id }
211
209
  app_is_running = app_info && app_info['process_state'] == 'Running'
212
210
  expect(app_is_running).to be(true)
211
+ expect(driver.instance_variable_get(:@running_apps)).not_to be_nil
213
212
  end
214
213
 
215
214
  it 'verifies that monkey_test works fine' do
216
- params = { udid: udid, bundle_id: bundle_id, duration: 1, session_path: Dir.pwd }
215
+ params = { udid: udid, bundle_id: bundle_id, event_count: 1, session_path: Dir.pwd }
217
216
  driver = described_class.new(params)
218
- expect(driver).to receive(:monkey_test_precondition)
219
217
  driver.monkey_test(Xcmonkey.new(params).gestures)
220
218
  expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
221
219
  end
@@ -227,8 +225,6 @@ describe Driver do
227
225
  { 'type' => 'swipe', 'x' => 12, 'y' => 12, 'endX' => 15, 'endY' => 15, 'duration' => 0.3 }
228
226
  ]
229
227
  driver = described_class.new(udid: udid, bundle_id: bundle_id, session_actions: session_actions)
230
- allow(Logger).to receive(:info).twice
231
- expect(driver).to receive(:monkey_test_precondition)
232
228
  expect(driver).to receive(:tap).with(coordinates: { x: 10, y: 10 })
233
229
  expect(driver).to receive(:press).with(coordinates: { x: 11, y: 11 }, duration: 1.4)
234
230
  expect(driver).to receive(:swipe).with(start_coordinates: { x: 12, y: 12 }, end_coordinates: { x: 15, y: 15 }, duration: 0.3)
@@ -238,7 +234,6 @@ describe Driver do
238
234
 
239
235
  it 'verifies that unknown actions does not break repeat_monkey_test' do
240
236
  driver = described_class.new(udid: udid, bundle_id: bundle_id, session_actions: [{ 'type' => 'test', 'x' => 10, 'y' => 10 }])
241
- allow(Logger).to receive(:info).twice
242
237
  expect(driver).to receive(:monkey_test_precondition)
243
238
  expect(driver).not_to receive(:tap)
244
239
  expect(driver).not_to receive(:press)
@@ -247,6 +242,90 @@ describe Driver do
247
242
  expect(driver.instance_variable_get(:@session)[:actions]).to be_empty
248
243
  end
249
244
 
245
+ it 'verifies that running apps are tracked' do
246
+ new_app_bundle_id = 'com.apple.Preferences'
247
+ driver.terminate_app(new_app_bundle_id)
248
+ driver.monkey_test_precondition
249
+ driver.launch_app(target_bundle_id: new_app_bundle_id, wait_for_state_update: true)
250
+ expect(driver).to receive(:launch_app).with(target_bundle_id: bundle_id)
251
+ expect(driver).to receive(:terminate_app).with(new_app_bundle_id)
252
+ driver.track_running_apps
253
+ end
254
+
255
+ it 'verifies that running apps can be determined' do
256
+ driver.terminate_app(bundle_id)
257
+ sum = driver.list_running_apps.size
258
+ driver.launch_app(target_bundle_id: bundle_id)
259
+ expect(driver.list_running_apps.size).to eq(sum + 1)
260
+ end
261
+
262
+ it 'verifies that app state change can be determined' do
263
+ driver.launch_app(target_bundle_id: bundle_id)
264
+ allow(driver).to receive(:detect_app_in_background).and_return(true)
265
+ expect(driver).not_to receive(:save_session)
266
+ expect(driver).to receive(:launch_app)
267
+ expect { driver.detect_app_state_change }.not_to raise_error
268
+ end
269
+
270
+ it 'verifies that background is the invalid app state' do
271
+ driver.terminate_app(bundle_id)
272
+ expect(driver).to receive(:save_session)
273
+ expect { driver.detect_app_state_change }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
274
+ end
275
+
276
+ it 'verifies that foreground is the valid app state' do
277
+ driver.launch_app(target_bundle_id: bundle_id, wait_for_state_update: true)
278
+ expect { driver.detect_app_state_change }.not_to raise_error
279
+ end
280
+
281
+ it 'verifies that app crashes can be ignored' do
282
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd, ignore_crashes: true)
283
+ driver.terminate_app(bundle_id)
284
+ expect(driver).not_to receive(:save_session)
285
+ expect(driver).to receive(:launch_app)
286
+ expect { driver.detect_app_state_change }.not_to raise_error
287
+ end
288
+
289
+ it 'verifies that background state can be determined' do
290
+ driver.terminate_app(bundle_id)
291
+ expect(driver.detect_app_in_background).to be(true)
292
+ end
293
+
294
+ it 'verifies that foregroung state can be determined' do
295
+ driver.monkey_test_precondition
296
+ expect(driver.detect_app_in_background).to be(false)
297
+ end
298
+
299
+ it 'verifies that xcmonkey behaves as expected on real devices' do
300
+ udid = '1234-5678'
301
+ driver = described_class.new(udid: udid, bundle_id: bundle_id)
302
+ allow(driver).to receive(:list_targets).and_return([{ 'udid' => udid, 'type' => 'device' }])
303
+ expect { driver.ensure_device_exists }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
304
+ end
305
+
306
+ it 'verifies that test can be slowed down' do
307
+ throttle = 1000
308
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd, throttle: throttle)
309
+ expect(driver).to receive(:sleep).with(throttle / 1000.0)
310
+ driver.check_speed_limit
311
+ end
312
+
313
+ it 'verifies that test ignores throttle by default' do
314
+ expect(driver).not_to receive(:sleep)
315
+ driver.check_speed_limit
316
+ end
317
+
318
+ it 'verifies that running apps are tracked on second entry with throttle' do
319
+ driver = described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd, throttle: 1)
320
+ expect(driver).to receive(:track_running_apps)
321
+ driver.checkup(1)
322
+ end
323
+
324
+ it 'verifies that running apps are not tracked on second entry without throttle' do
325
+ expect(driver).not_to receive(:track_running_apps)
326
+ driver.checkup(1)
327
+ end
328
+
250
329
  it 'verifies that simulator was not booted' do
251
330
  driver.shutdown_simulator
252
331
  error_message = "Failed to boot #{udid}"
@@ -1,11 +1,11 @@
1
1
  describe Repeater do
2
2
  let(:session_path) { 'test/path/session.json' }
3
- let(:session_file_content_full) { '{ "params": {"udid": "0", "bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
3
+ let(:session_file_content_full) { '{ "params": {"udid": "0", "bundle_id": "0", "disable_simulator_keyboard": false}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
4
4
  let(:session_file_content_without_params) { '{ "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
5
- let(:session_file_content_with_empty_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [] }' }
6
- let(:session_file_content_without_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "enable_simulator_keyboard": true} }' }
7
- let(:session_file_content_without_bundle_id) { '{ "params": {"udid": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
8
- let(:session_file_content_without_udid) { '{ "params": {"bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
5
+ let(:session_file_content_with_empty_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "disable_simulator_keyboard": false}, "actions": [] }' }
6
+ let(:session_file_content_without_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "disable_simulator_keyboard": false} }' }
7
+ let(:session_file_content_without_bundle_id) { '{ "params": {"udid": "0", "disable_simulator_keyboard": false}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
8
+ let(:session_file_content_without_udid) { '{ "params": {"bundle_id": "0", "disable_simulator_keyboard": false}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
9
9
 
10
10
  it 'verifies that session cannot be validated without params' do
11
11
  allow(File).to receive(:exist?).and_return(true)
@@ -1,6 +1,10 @@
1
1
  describe Xcmonkey do
2
- let(:params) { { udid: '123', bundle_id: 'example.com.app', duration: 10, session_path: Dir.pwd } }
3
- let(:duration_error_msg) { 'Duration must be Integer and not less than 1 second' }
2
+ let(:params) { { udid: '123', bundle_id: 'example.com.app', event_count: 10, session_path: Dir.pwd } }
3
+ let(:event_count_error_msg) { 'Event count must be Integer and not less than 1' }
4
+
5
+ before do
6
+ allow(Logger).to receive(:info)
7
+ end
4
8
 
5
9
  it 'verifies gestures' do
6
10
  gestures = described_class.new(params).gestures
@@ -10,6 +14,30 @@ describe Xcmonkey do
10
14
  expect(gestures) =~ presses + taps + swipes
11
15
  end
12
16
 
17
+ it 'verifies gestures without taps' do
18
+ params[:exclude_taps] = true
19
+ gestures = described_class.new(params).gestures
20
+ swipes = [:precise_swipe, :blind_swipe] * 5
21
+ presses = [:precise_press, :blind_press]
22
+ expect(gestures) =~ presses + swipes
23
+ end
24
+
25
+ it 'verifies gestures without swipes' do
26
+ params[:exclude_swipes] = true
27
+ gestures = described_class.new(params).gestures
28
+ taps = [:precise_tap, :blind_tap] * 10
29
+ presses = [:precise_press, :blind_press]
30
+ expect(gestures) =~ presses + taps
31
+ end
32
+
33
+ it 'verifies gestures without presses' do
34
+ params[:exclude_presses] = true
35
+ gestures = described_class.new(params).gestures
36
+ taps = [:precise_tap, :blind_tap] * 10
37
+ swipes = [:precise_swipe, :blind_swipe] * 5
38
+ expect(gestures) =~ swipes + taps
39
+ end
40
+
13
41
  it 'verifies required params' do
14
42
  expect(Logger).not_to receive(:error)
15
43
  described_class.new(params)
@@ -27,21 +55,21 @@ describe Xcmonkey do
27
55
  described_class.new(params)
28
56
  end
29
57
 
30
- it 'verifies `duration` param is optional' do
31
- params[:duration] = nil
58
+ it 'verifies `event_count` param is optional' do
59
+ params[:event_count] = nil
32
60
  expect(Logger).not_to receive(:error)
33
61
  described_class.new(params)
34
62
  end
35
63
 
36
- it 'verifies `duration` param cannot be equal to zero' do
37
- params[:duration] = 0
38
- expect(Logger).to receive(:error).with(duration_error_msg)
64
+ it 'verifies `event_count` param cannot be equal to zero' do
65
+ params[:event_count] = 0
66
+ expect(Logger).to receive(:error).with(event_count_error_msg)
39
67
  described_class.new(params)
40
68
  end
41
69
 
42
- it 'verifies `duration` param cannot be negative' do
43
- params[:duration] = -1
44
- expect(Logger).to receive(:error).with(duration_error_msg)
70
+ it 'verifies `event_count` param cannot be negative' do
71
+ params[:event_count] = -1
72
+ expect(Logger).to receive(:error).with(event_count_error_msg)
45
73
  described_class.new(params)
46
74
  end
47
75
 
data/xcmonkey.gemspec CHANGED
@@ -1,42 +1,32 @@
1
1
  lib = File.expand_path('lib', __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require "xcmonkey/version"
3
+ require 'xcmonkey/version'
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "xcmonkey"
6
+ spec.name = 'xcmonkey'
7
7
  spec.version = Xcmonkey::VERSION
8
- spec.authors = ["alteral"]
9
- spec.email = ["a.alterpesotskiy@mail.ru"]
8
+ spec.authors = ['alteral']
9
+ spec.email = ['a.alterpesotskiy@mail.ru']
10
10
 
11
- spec.summary = "xcmonkey is a tool for doing randomised UI testing of iOS apps"
12
- spec.homepage = "https://github.com/alteral/xcmonkey"
13
- spec.license = "MIT"
11
+ spec.summary = 'xcmonkey is a tool for doing randomised UI testing of iOS apps'
12
+ spec.homepage = 'https://github.com/alteral/xcmonkey'
13
+ spec.license = 'MIT'
14
14
 
15
15
  if spec.respond_to?(:metadata)
16
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
16
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
17
17
  else
18
- raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
18
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
19
19
  end
20
20
 
21
- spec.bindir = "bin"
22
- spec.executables = ["xcmonkey"]
21
+ spec.bindir = 'bin'
22
+ spec.executables = ['xcmonkey']
23
23
  spec.files = `git ls-files -z`.split("\x0")
24
24
  spec.require_paths = ['lib']
25
25
 
26
26
  spec.required_ruby_version = '>= 2.4'
27
- spec.add_development_dependency('bundler')
28
- spec.add_development_dependency('fasterer', '0.9.0')
29
- spec.add_development_dependency('fastlane')
30
- spec.add_development_dependency('rake')
31
- spec.add_development_dependency('rspec')
32
- spec.add_development_dependency('rspec_junit_formatter')
33
- spec.add_development_dependency('rubocop', '1.38')
34
- spec.add_development_dependency('rubocop-performance')
35
- spec.add_development_dependency('rubocop-rake', '0.6.0')
36
- spec.add_development_dependency('rubocop-require_tools')
37
- spec.add_development_dependency('rubocop-rspec', '2.15.0')
38
- spec.add_development_dependency('simplecov')
39
- spec.add_dependency("colorize", "~> 0.8.1")
40
- spec.add_dependency("commander")
41
- spec.metadata['rubygems_mfa_required'] = 'true'
27
+
28
+ spec.add_dependency('colorize', '~> 0.8.1')
29
+ spec.add_dependency('commander')
30
+
31
+ spec.metadata['rubygems_mfa_required'] = 'true'
42
32
  end
metadata CHANGED
@@ -1,183 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcmonkey
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - alteral
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-20 00:00:00.000000000 Z
11
+ date: 2023-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: fasterer
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - '='
32
- - !ruby/object:Gem::Version
33
- version: 0.9.0
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - '='
39
- - !ruby/object:Gem::Version
40
- version: 0.9.0
41
- - !ruby/object:Gem::Dependency
42
- name: fastlane
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: rake
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rspec
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: rspec_junit_formatter
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - '='
102
- - !ruby/object:Gem::Version
103
- version: '1.38'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - '='
109
- - !ruby/object:Gem::Version
110
- version: '1.38'
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop-performance
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop-rake
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - '='
130
- - !ruby/object:Gem::Version
131
- version: 0.6.0
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - '='
137
- - !ruby/object:Gem::Version
138
- version: 0.6.0
139
- - !ruby/object:Gem::Dependency
140
- name: rubocop-require_tools
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
- - !ruby/object:Gem::Dependency
154
- name: rubocop-rspec
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - '='
158
- - !ruby/object:Gem::Version
159
- version: 2.15.0
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - '='
165
- - !ruby/object:Gem::Version
166
- version: 2.15.0
167
- - !ruby/object:Gem::Dependency
168
- name: simplecov
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - ">="
172
- - !ruby/object:Gem::Version
173
- version: '0'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - ">="
179
- - !ruby/object:Gem::Version
180
- version: '0'
181
13
  - !ruby/object:Gem::Dependency
182
14
  name: colorize
183
15
  requirement: !ruby/object:Gem::Requirement
@@ -220,10 +52,12 @@ files:
220
52
  - ".github/ISSUE_TEMPLATE/feature-request.md"
221
53
  - ".github/dependabot.yml"
222
54
  - ".github/pull_request_template.md"
55
+ - ".github/stale.yml"
223
56
  - ".github/workflows/test.yml"
224
57
  - ".gitignore"
225
58
  - ".rspec"
226
59
  - ".rubocop.yml"
60
+ - CHANGELOG.md
227
61
  - CODE_OF_CONDUCT.md
228
62
  - Gemfile
229
63
  - LICENSE