xcmonkey 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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