xcmonkey 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/{issue_template → ISSUE_TEMPLATE}/bug_report.md +0 -0
- data/.github/{issue_template/feature_request.md → ISSUE_TEMPLATE/feature-request.md} +1 -1
- data/.github/workflows/test.yml +3 -2
- data/README.md +42 -50
- data/bin/xcmonkey +1 -6
- data/lib/xcmonkey/describer.rb +16 -16
- data/lib/xcmonkey/driver.rb +80 -46
- data/lib/xcmonkey/repeater.rb +28 -28
- data/lib/xcmonkey/version.rb +2 -3
- data/lib/xcmonkey.rb +26 -25
- data/spec/describer_spec.rb +4 -2
- data/spec/driver_spec.rb +112 -34
- data/spec/repeater_spec.rb +0 -1
- data/spec/xcmonkey_spec.rb +53 -55
- data/xcmonkey.gemspec +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c93c94403332c085a393877d88d354b2736788f6e52d118eb8b9faa70c91b1e5
|
|
4
|
+
data.tar.gz: ee0f3bfcd014151b4821a6df50f601205581ebd480b5626e33588bda2928981a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0ab88db343b0e83363130a9e9c8ed965acbf9d58f56d597b301ff12da4df9d4e79b08c3b2284b870a65c9bfc3d05a8ba9df0d19409631620a28836c9d5785814
|
|
7
|
+
data.tar.gz: fe46af5c215273be9210f3c880617457a12c6063e0bd1901d6abbd10750f10935b77a4797ff374926c3f1cac6b1ef79032f210fb705e05917146e4c64cede97c
|
|
File without changes
|
data/.github/workflows/test.yml
CHANGED
|
@@ -17,10 +17,11 @@ jobs:
|
|
|
17
17
|
env:
|
|
18
18
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
19
19
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
20
|
+
PR_NUMBER: ${{ github.event.number }}
|
|
20
21
|
steps:
|
|
21
|
-
- uses: actions/checkout@v3.
|
|
22
|
+
- uses: actions/checkout@v3.3.0
|
|
22
23
|
|
|
23
|
-
- uses: actions/setup-python@v4.
|
|
24
|
+
- uses: actions/setup-python@v4.5.0
|
|
24
25
|
with:
|
|
25
26
|
python-version: 3.11
|
|
26
27
|
cache: 'pip'
|
data/README.md
CHANGED
|
@@ -39,10 +39,32 @@ gem 'xcmonkey'
|
|
|
39
39
|
### To run a stress test
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
$ xcmonkey test --
|
|
43
|
-
|
|
42
|
+
$ xcmonkey test --duration 100 --bundle-id "com.apple.Maps" --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA"
|
|
43
|
+
|
|
44
|
+
12:44:19.343: Device info: {
|
|
45
|
+
"name": "iPhone 14 Pro",
|
|
46
|
+
"udid": "413EA256-CFFB-4312-94A6-12592BEE4CBA",
|
|
47
|
+
"state": "Booted",
|
|
48
|
+
"type": "simulator",
|
|
49
|
+
"os_version": "iOS 16.2",
|
|
50
|
+
"architecture": "x86_64",
|
|
51
|
+
"path": "/tmp/idb/413EA256-CFFB-4312-94A6-12592BEE4CBA_companion.sock",
|
|
52
|
+
"is_local": true,
|
|
53
|
+
"companion": "/tmp/idb/413EA256-CFFB-4312-94A6-12592BEE4CBA_companion.sock"
|
|
54
|
+
}
|
|
44
55
|
|
|
45
|
-
12:44:22.550: App info:
|
|
56
|
+
12:44:22.550: App info: {
|
|
57
|
+
"bundle_id": "com.apple.Maps",
|
|
58
|
+
"name": "Maps",
|
|
59
|
+
"install_type": "system",
|
|
60
|
+
"architectures": [
|
|
61
|
+
"x86_64",
|
|
62
|
+
"arm64"
|
|
63
|
+
],
|
|
64
|
+
"process_state": "Running",
|
|
65
|
+
"debuggable": false,
|
|
66
|
+
"pid": "49186"
|
|
67
|
+
}
|
|
46
68
|
|
|
47
69
|
12:44:23.203: Tap: {
|
|
48
70
|
"x": 53,
|
|
@@ -66,59 +88,29 @@ $ xcmonkey test --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA" --bundle-id "com.a
|
|
|
66
88
|
### To repeat the stress test from generated session
|
|
67
89
|
|
|
68
90
|
```bash
|
|
69
|
-
|
|
70
|
-
12:48:13.333: Device info: iPhone 14 Pro | 413EA256-CFFB-4312-94A6-12592BEE4CBA | Booted | simulator | iOS 16.2 | x86_64 | /tmp/idb/413EA256-CFFB-4312-94A6-12592BEE4CBA_companion.sock
|
|
71
|
-
|
|
72
|
-
12:48:16.542: App info: com.apple.Maps | Maps | system | arm64, x86_64 | Running | Not Debuggable | pid=73416
|
|
73
|
-
|
|
74
|
-
12:48:20.195: Tap: {
|
|
75
|
-
"x": 53,
|
|
76
|
-
"y": 749
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
12:48:20.404: Swipe (0.5s): {
|
|
80
|
-
"x": 196,
|
|
81
|
-
"y": 426
|
|
82
|
-
} => {
|
|
83
|
-
"x": 143,
|
|
84
|
-
"y": 447
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
12:48:21.155: Press (1.2s): {
|
|
88
|
-
"x": 143,
|
|
89
|
-
"y": 323
|
|
90
|
-
}
|
|
91
|
+
xcmonkey repeat --session-path "./xcmonkey-session.json"
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
### To describe the required point
|
|
94
95
|
|
|
95
96
|
```bash
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
20:05:21.713: x:20 y:625 point info: {
|
|
100
|
-
"AXFrame": "{{19, 624.3}, {86, 130.6}}",
|
|
101
|
-
"AXUniqueId": "ShortcutsRowCell",
|
|
102
|
-
"frame": {
|
|
103
|
-
"y": 624.3,
|
|
104
|
-
"x": 19,
|
|
105
|
-
"width": 86,
|
|
106
|
-
"height": 130.6
|
|
107
|
-
},
|
|
108
|
-
"role_description": "button",
|
|
109
|
-
"AXLabel": "Home",
|
|
110
|
-
"content_required": false,
|
|
111
|
-
"type": "Button",
|
|
112
|
-
"title": null,
|
|
113
|
-
"help": null,
|
|
114
|
-
"custom_actions": [
|
|
97
|
+
xcmonkey describe -x 20 -y 625 --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA"
|
|
98
|
+
```
|
|
115
99
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
100
|
+
## [fastlane](https://github.com/fastlane/fastlane) integration
|
|
101
|
+
|
|
102
|
+
To run *xcmonkey* from *fastlane*, add the following code to your `Fastfile`:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
require 'xcmonkey'
|
|
106
|
+
|
|
107
|
+
lane :test do
|
|
108
|
+
Xcmonkey.new(
|
|
109
|
+
duration: 100,
|
|
110
|
+
bundle_id: 'com.apple.Maps',
|
|
111
|
+
udid: '413EA256-CFFB-4312-94A6-12592BEE4CBA'
|
|
112
|
+
).run
|
|
113
|
+
end
|
|
122
114
|
```
|
|
123
115
|
|
|
124
116
|
## Code of Conduct
|
data/bin/xcmonkey
CHANGED
|
@@ -8,7 +8,7 @@ require_relative '../lib/xcmonkey/logger'
|
|
|
8
8
|
require_relative '../lib/xcmonkey/driver'
|
|
9
9
|
require_relative '../lib/xcmonkey/version'
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
class Xcmonkey
|
|
12
12
|
program :version, VERSION
|
|
13
13
|
program :description, 'xcmonkey is a tool for doing randomised UI testing of iOS apps'
|
|
14
14
|
|
|
@@ -21,11 +21,6 @@ module Xcmonkey
|
|
|
21
21
|
c.option('-k', '--enable-simulator-keyboard', 'Should simulator keyboard be enabled? Defaults to `true`')
|
|
22
22
|
c.option('-s', '--session-path STRING', String, 'Path where monkey testing session should be saved. Defaults to current directory')
|
|
23
23
|
c.action do |_, options|
|
|
24
|
-
options.default(
|
|
25
|
-
duration: 60,
|
|
26
|
-
session_path: Dir.pwd,
|
|
27
|
-
enable_simulator_keyboard: true
|
|
28
|
-
)
|
|
29
24
|
params = {
|
|
30
25
|
udid: options.udid,
|
|
31
26
|
bundle_id: options.bundle_id,
|
data/lib/xcmonkey/describer.rb
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
class Describer
|
|
2
|
-
|
|
2
|
+
attr_accessor :x, :y, :driver
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
def initialize(params)
|
|
5
|
+
ensure_required_params(params)
|
|
6
|
+
self.x = params[:x]
|
|
7
|
+
self.y = params[:y]
|
|
8
|
+
self.driver = Driver.new(params)
|
|
9
|
+
end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
def run
|
|
12
|
+
driver.ensure_device_exists
|
|
13
|
+
driver.describe_point(x, y)
|
|
14
|
+
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
def ensure_required_params(params)
|
|
17
|
+
Logger.error('UDID should be provided') if params[:udid].nil?
|
|
18
|
+
Logger.error('`x` point coordinate should be provided') if params[:x].nil? || params[:x].to_i.to_s != params[:x].to_s
|
|
19
|
+
Logger.error('`y` point coordinate should be provided') if params[:y].nil? || params[:y].to_i.to_s != params[:y].to_s
|
|
20
|
+
end
|
|
21
21
|
end
|
data/lib/xcmonkey/driver.rb
CHANGED
|
@@ -13,17 +13,19 @@ class Driver
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def monkey_test_precondition
|
|
16
|
+
puts
|
|
16
17
|
ensure_device_exists
|
|
17
18
|
ensure_app_installed
|
|
18
|
-
terminate_app
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
terminate_app(bundle_id)
|
|
20
|
+
launch_app(target_bundle_id: bundle_id, wait_for_state_update: true)
|
|
21
|
+
@running_apps = list_running_apps
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def monkey_test(gestures)
|
|
24
25
|
monkey_test_precondition
|
|
25
26
|
app_elements = describe_ui.shuffle
|
|
26
27
|
current_time = Time.now
|
|
28
|
+
counter = 0
|
|
27
29
|
while Time.now < current_time + session_duration
|
|
28
30
|
el1_coordinates = central_coordinates(app_elements.first)
|
|
29
31
|
el2_coordinates = central_coordinates(app_elements.last)
|
|
@@ -51,17 +53,17 @@ class Driver
|
|
|
51
53
|
else
|
|
52
54
|
next
|
|
53
55
|
end
|
|
56
|
+
detect_app_state_change
|
|
57
|
+
track_running_apps if counter % 5 == 0 # Track running apps after every 5th action to speed up the test
|
|
58
|
+
counter += 1
|
|
54
59
|
app_elements = describe_ui.shuffle
|
|
55
|
-
next unless app_elements.include?(@home_tracker)
|
|
56
|
-
|
|
57
|
-
save_session
|
|
58
|
-
Logger.error('App lost')
|
|
59
60
|
end
|
|
60
61
|
save_session
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def repeat_monkey_test
|
|
64
65
|
monkey_test_precondition
|
|
66
|
+
counter = 0
|
|
65
67
|
session_actions.each do |action|
|
|
66
68
|
case action['type']
|
|
67
69
|
when 'tap'
|
|
@@ -74,16 +76,15 @@ class Driver
|
|
|
74
76
|
end_coordinates: { x: action['endX'], y: action['endY'] },
|
|
75
77
|
duration: action['duration']
|
|
76
78
|
)
|
|
79
|
+
else
|
|
80
|
+
next
|
|
77
81
|
end
|
|
78
|
-
|
|
82
|
+
detect_app_state_change
|
|
83
|
+
track_running_apps if counter % 5 == 0
|
|
84
|
+
counter += 1
|
|
79
85
|
end
|
|
80
86
|
end
|
|
81
87
|
|
|
82
|
-
def open_home_screen(with_tracker: false)
|
|
83
|
-
`idb ui button --udid #{udid} HOME`
|
|
84
|
-
detect_home_unique_element if with_tracker
|
|
85
|
-
end
|
|
86
|
-
|
|
87
88
|
def describe_ui
|
|
88
89
|
JSON.parse(`idb ui describe-all --udid #{udid}`)
|
|
89
90
|
end
|
|
@@ -94,13 +95,13 @@ class Driver
|
|
|
94
95
|
point_info
|
|
95
96
|
end
|
|
96
97
|
|
|
97
|
-
def launch_app
|
|
98
|
-
`idb launch --udid #{udid} #{
|
|
99
|
-
wait_until_app_launched
|
|
98
|
+
def launch_app(target_bundle_id:, wait_for_state_update: false)
|
|
99
|
+
`idb launch --udid #{udid} #{target_bundle_id}`
|
|
100
|
+
wait_until_app_launched(target_bundle_id) if wait_for_state_update
|
|
100
101
|
end
|
|
101
102
|
|
|
102
|
-
def terminate_app
|
|
103
|
-
`idb terminate --udid #{udid} #{
|
|
103
|
+
def terminate_app(target_bundle_id)
|
|
104
|
+
`idb terminate --udid #{udid} #{target_bundle_id} 2>/dev/null`
|
|
104
105
|
end
|
|
105
106
|
|
|
106
107
|
def boot_simulator
|
|
@@ -119,33 +120,38 @@ class Driver
|
|
|
119
120
|
end
|
|
120
121
|
|
|
121
122
|
def list_targets
|
|
122
|
-
@
|
|
123
|
-
@
|
|
123
|
+
@targets ||= `idb list-targets --json`.split("\n").map! { |target| JSON.parse(target) }
|
|
124
|
+
@targets
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def list_apps
|
|
128
|
+
`idb list-apps --udid #{udid} --json`.split("\n").map! { |app| JSON.parse(app) }
|
|
124
129
|
end
|
|
125
130
|
|
|
126
|
-
def
|
|
127
|
-
|
|
131
|
+
def list_running_apps
|
|
132
|
+
list_apps.select { |app| app['process_state'] == 'Running' }
|
|
128
133
|
end
|
|
129
134
|
|
|
130
135
|
def ensure_app_installed
|
|
131
|
-
|
|
136
|
+
return if list_apps.any? { |app| app['bundle_id'] == bundle_id }
|
|
137
|
+
|
|
138
|
+
Logger.error("App #{bundle_id} is not installed on device #{udid}")
|
|
132
139
|
end
|
|
133
140
|
|
|
134
141
|
def ensure_device_exists
|
|
135
|
-
device = list_targets.detect { |target| target
|
|
142
|
+
device = list_targets.detect { |target| target['udid'] == udid }
|
|
136
143
|
Logger.error("Can't find device #{udid}") if device.nil?
|
|
137
144
|
|
|
138
|
-
Logger.info('Device info:', payload: device)
|
|
139
|
-
if device
|
|
145
|
+
Logger.info('Device info:', payload: JSON.pretty_generate(device))
|
|
146
|
+
if device['type'] == 'simulator'
|
|
140
147
|
configure_simulator_keyboard
|
|
141
148
|
boot_simulator
|
|
149
|
+
else
|
|
150
|
+
Logger.error('xcmonkey does not support real devices yet. ' \
|
|
151
|
+
'For more information see https://github.com/alteral/xcmonkey/issues/7')
|
|
142
152
|
end
|
|
143
153
|
end
|
|
144
154
|
|
|
145
|
-
def list_apps
|
|
146
|
-
`idb list-apps --udid #{udid}`
|
|
147
|
-
end
|
|
148
|
-
|
|
149
155
|
def tap(coordinates:)
|
|
150
156
|
Logger.info('Tap:', payload: JSON.pretty_generate(coordinates))
|
|
151
157
|
@session[:actions] << { type: :tap, x: coordinates[:x], y: coordinates[:y] } unless session_actions
|
|
@@ -219,29 +225,57 @@ class Driver
|
|
|
219
225
|
File.write("#{session_path}/xcmonkey-session.json", JSON.pretty_generate(@session))
|
|
220
226
|
end
|
|
221
227
|
|
|
222
|
-
|
|
228
|
+
# This function takes ≈200ms
|
|
229
|
+
def track_running_apps
|
|
230
|
+
current_list_of_running_apps = list_running_apps
|
|
231
|
+
if @running_apps != current_list_of_running_apps
|
|
232
|
+
currently_running_bundle_ids = current_list_of_running_apps.map { |app| app['bundle_id'] }
|
|
233
|
+
previously_running_bundle_ids = @running_apps.map { |app| app['bundle_id'] }
|
|
234
|
+
new_apps = currently_running_bundle_ids - previously_running_bundle_ids
|
|
223
235
|
|
|
224
|
-
|
|
225
|
-
|
|
236
|
+
return if new_apps.empty?
|
|
237
|
+
|
|
238
|
+
launch_app(target_bundle_id: bundle_id)
|
|
239
|
+
new_apps.each do |id|
|
|
240
|
+
Logger.warn("Shutting down: #{id}")
|
|
241
|
+
terminate_app(id)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
226
244
|
end
|
|
227
245
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
246
|
+
# This function takes ≈300ms
|
|
247
|
+
def detect_app_state_change
|
|
248
|
+
return unless detect_app_in_background
|
|
249
|
+
|
|
250
|
+
target_app_is_running = list_running_apps.any? { |app| app['bundle_id'] == bundle_id }
|
|
251
|
+
|
|
252
|
+
if target_app_is_running
|
|
253
|
+
launch_app(target_bundle_id: bundle_id)
|
|
254
|
+
else
|
|
255
|
+
save_session
|
|
256
|
+
Logger.error("Target app has crashed or been terminated")
|
|
232
257
|
end
|
|
233
|
-
@home_tracker
|
|
234
258
|
end
|
|
235
259
|
|
|
236
|
-
def
|
|
237
|
-
|
|
260
|
+
def detect_app_in_background
|
|
261
|
+
current_app_label = describe_ui.detect { |el| el['type'] == 'Application' }['AXLabel']
|
|
262
|
+
current_app_label.nil? || current_app_label.strip.empty?
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
private
|
|
266
|
+
|
|
267
|
+
def ensure_driver_installed
|
|
268
|
+
Logger.error("'idb' doesn't seem to be installed") if `which idb`.strip.empty?
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def wait_until_app_launched(target_bundle_id)
|
|
272
|
+
app_is_running = false
|
|
238
273
|
current_time = Time.now
|
|
239
|
-
while
|
|
240
|
-
app_info = list_apps.
|
|
241
|
-
|
|
242
|
-
end
|
|
274
|
+
while !app_is_running && Time.now < current_time + 5
|
|
275
|
+
app_info = list_apps.detect { |app| app['bundle_id'] == target_bundle_id }
|
|
276
|
+
app_is_running = app_info && app_info['process_state'] == 'Running'
|
|
243
277
|
end
|
|
244
|
-
Logger.error("Can't run the app #{
|
|
245
|
-
Logger.info('App info:', payload: app_info)
|
|
278
|
+
Logger.error("Can't run the app #{target_bundle_id}") unless app_is_running
|
|
279
|
+
Logger.info('App info:', payload: JSON.pretty_generate(app_info))
|
|
246
280
|
end
|
|
247
281
|
end
|
data/lib/xcmonkey/repeater.rb
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
class Repeater
|
|
2
|
-
|
|
2
|
+
attr_accessor :udid, :bundle_id, :enable_simulator_keyboard, :actions
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
def initialize(params)
|
|
5
|
+
validate_session(params[:session_path])
|
|
6
|
+
end
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
def run
|
|
9
|
+
params = {
|
|
10
|
+
udid: udid,
|
|
11
|
+
bundle_id: bundle_id,
|
|
12
|
+
enable_simulator_keyboard: enable_simulator_keyboard,
|
|
13
|
+
session_actions: actions
|
|
14
|
+
}
|
|
15
|
+
Driver.new(params).repeat_monkey_test
|
|
16
|
+
end
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
def validate_session(session_path)
|
|
19
|
+
Logger.error("Provided session can't be found: #{session_path}") unless File.exist?(session_path)
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
session = JSON.parse(File.read(session_path))
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
if session['params'].nil?
|
|
24
|
+
Logger.error('Provided session is not valid: `params` should not be `nil`')
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
self.actions = session['actions']
|
|
29
|
+
Logger.error('Provided session is not valid: `actions` should not be `nil` or `empty`') if actions.nil? || actions.empty?
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
self.udid = session['params']['udid']
|
|
32
|
+
Logger.error('Provided session is not valid: `udid` should not be `nil`') if udid.nil?
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
self.bundle_id = session['params']['bundle_id']
|
|
35
|
+
Logger.error('Provided session is not valid: `bundle_id` should not be `nil`') if bundle_id.nil?
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
self.enable_simulator_keyboard = session['params']['enable_simulator_keyboard']
|
|
38
|
+
end
|
|
39
39
|
end
|
data/lib/xcmonkey/version.rb
CHANGED
data/lib/xcmonkey.rb
CHANGED
|
@@ -6,36 +6,37 @@ require_relative 'xcmonkey/version'
|
|
|
6
6
|
require_relative 'xcmonkey/logger'
|
|
7
7
|
require_relative 'xcmonkey/driver'
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
attr_accessor :driver
|
|
9
|
+
class Xcmonkey
|
|
10
|
+
attr_accessor :driver
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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)
|
|
17
|
+
self.driver = Driver.new(params)
|
|
18
|
+
end
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
def run
|
|
21
|
+
driver.monkey_test(gestures)
|
|
22
|
+
end
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
def gestures
|
|
25
|
+
taps = [:precise_tap, :blind_tap] * 10
|
|
26
|
+
swipes = [:precise_swipe, :blind_swipe] * 5
|
|
27
|
+
presses = [:precise_press, :blind_press]
|
|
28
|
+
taps + swipes + presses
|
|
29
|
+
end
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
def ensure_required_params(params)
|
|
32
|
+
Logger.error('UDID should be provided') if params[:udid].nil?
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
Logger.error('Bundle identifier should be provided') if params[:bundle_id].nil?
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
Logger.error('Session path should be a directory') if params[:session_path].nil? || !File.directory?(params[:session_path])
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
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')
|
|
40
|
+
end
|
|
41
|
+
end
|
|
41
42
|
end
|
data/spec/describer_spec.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
@@ -4,36 +4,22 @@ describe Driver do
|
|
|
4
4
|
let(:driver) { described_class.new(udid: udid, bundle_id: bundle_id, session_path: Dir.pwd) }
|
|
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)
|
|
10
14
|
expect { driver.boot_simulator }.not_to raise_error
|
|
11
15
|
end
|
|
12
16
|
|
|
13
|
-
it 'verifies that there are booted simulators' do
|
|
14
|
-
driver.boot_simulator
|
|
15
|
-
booted_simulators = driver.list_booted_simulators
|
|
16
|
-
expect(booted_simulators).not_to be_empty
|
|
17
|
-
end
|
|
18
|
-
|
|
19
17
|
it 'verifies that ui can be described' do
|
|
20
18
|
driver.boot_simulator
|
|
21
19
|
ui = driver.describe_ui
|
|
22
20
|
expect(ui).not_to be_empty
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
it 'verifies that home screen can be opened' do
|
|
26
|
-
driver.boot_simulator
|
|
27
|
-
home_tracker = driver.open_home_screen(with_tracker: true)
|
|
28
|
-
expect(home_tracker).not_to be_empty
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
it 'verifies that home screen can be opened without tracker' do
|
|
32
|
-
driver.boot_simulator
|
|
33
|
-
home_tracker = driver.open_home_screen(with_tracker: false)
|
|
34
|
-
expect(home_tracker).to be_nil
|
|
35
|
-
end
|
|
36
|
-
|
|
37
23
|
it 'verifies that list of targets can be showed' do
|
|
38
24
|
list_targets = driver.list_targets
|
|
39
25
|
expect(list_targets).not_to be_empty
|
|
@@ -41,8 +27,8 @@ describe Driver do
|
|
|
41
27
|
|
|
42
28
|
it 'verifies that list of apps can be showed' do
|
|
43
29
|
driver.boot_simulator
|
|
44
|
-
|
|
45
|
-
expect(
|
|
30
|
+
app_exists = driver.list_apps.any? { |app| app['bundle_id'] == bundle_id }
|
|
31
|
+
expect(app_exists).to be(true)
|
|
46
32
|
end
|
|
47
33
|
|
|
48
34
|
it 'verifies that app installed' do
|
|
@@ -62,11 +48,9 @@ describe Driver do
|
|
|
62
48
|
end
|
|
63
49
|
|
|
64
50
|
it 'verifies that device exists' do
|
|
65
|
-
|
|
66
|
-
payload = driver.list_targets.detect { |target| target.include?(udid) }
|
|
67
|
-
expect(Logger).not_to receive(:error).with(error_message, payload: nil)
|
|
68
|
-
expect(Logger).to receive(:info).with('Device info:', payload: payload)
|
|
51
|
+
expect(Logger).not_to receive(:error)
|
|
69
52
|
expect(driver).to receive(:boot_simulator)
|
|
53
|
+
expect(driver).to receive(:configure_simulator_keyboard)
|
|
70
54
|
expect { driver.ensure_device_exists }.not_to raise_error
|
|
71
55
|
end
|
|
72
56
|
|
|
@@ -143,18 +127,25 @@ describe Driver do
|
|
|
143
127
|
expect(keyboard_state.first).to include('1')
|
|
144
128
|
end
|
|
145
129
|
|
|
146
|
-
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
|
|
147
139
|
expect(Logger).not_to receive(:error)
|
|
148
|
-
expect(
|
|
140
|
+
expect(driver).not_to receive(:wait_until_app_launched)
|
|
149
141
|
driver.boot_simulator
|
|
150
|
-
driver.terminate_app
|
|
151
|
-
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
|
|
152
144
|
end
|
|
153
145
|
|
|
154
146
|
it 'verifies tap in new session' do
|
|
155
147
|
driver.boot_simulator
|
|
156
148
|
coordinates = { x: 1, y: 1 }
|
|
157
|
-
expect(Logger).to receive(:info).with('Tap:', payload: JSON.pretty_generate(coordinates))
|
|
158
149
|
driver.tap(coordinates: coordinates)
|
|
159
150
|
expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
|
|
160
151
|
end
|
|
@@ -162,7 +153,6 @@ describe Driver do
|
|
|
162
153
|
it 'verifies tap in old session' do
|
|
163
154
|
driver_with_session.boot_simulator
|
|
164
155
|
coordinates = { x: 1, y: 1 }
|
|
165
|
-
expect(Logger).to receive(:info).with('Tap:', payload: JSON.pretty_generate(coordinates))
|
|
166
156
|
driver_with_session.tap(coordinates: coordinates)
|
|
167
157
|
expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
|
|
168
158
|
end
|
|
@@ -171,7 +161,6 @@ describe Driver do
|
|
|
171
161
|
driver.boot_simulator
|
|
172
162
|
duration = 0.5
|
|
173
163
|
coordinates = { x: 1, y: 1 }
|
|
174
|
-
expect(Logger).to receive(:info).with("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
|
|
175
164
|
driver.press(coordinates: coordinates, duration: duration)
|
|
176
165
|
expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
|
|
177
166
|
end
|
|
@@ -180,7 +169,6 @@ describe Driver do
|
|
|
180
169
|
driver_with_session.boot_simulator
|
|
181
170
|
duration = 0.5
|
|
182
171
|
coordinates = { x: 1, y: 1 }
|
|
183
|
-
expect(Logger).to receive(:info).with("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates))
|
|
184
172
|
driver_with_session.press(coordinates: coordinates, duration: duration)
|
|
185
173
|
expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
|
|
186
174
|
end
|
|
@@ -190,7 +178,6 @@ describe Driver do
|
|
|
190
178
|
duration = 0.5
|
|
191
179
|
start_coordinates = { x: 1, y: 1 }
|
|
192
180
|
end_coordinates = { x: 2, y: 2 }
|
|
193
|
-
expect(Logger).to receive(:info).with("Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}")
|
|
194
181
|
driver.swipe(start_coordinates: start_coordinates, end_coordinates: end_coordinates, duration: duration)
|
|
195
182
|
expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
|
|
196
183
|
end
|
|
@@ -200,7 +187,6 @@ describe Driver do
|
|
|
200
187
|
duration = 0.5
|
|
201
188
|
start_coordinates = { x: 1, y: 1 }
|
|
202
189
|
end_coordinates = { x: 2, y: 2 }
|
|
203
|
-
expect(Logger).to receive(:info).with("Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}")
|
|
204
190
|
driver_with_session.swipe(start_coordinates: start_coordinates, end_coordinates: end_coordinates, duration: duration)
|
|
205
191
|
expect(driver_with_session.instance_variable_get(:@session)[:actions]).to be_empty
|
|
206
192
|
end
|
|
@@ -211,6 +197,98 @@ describe Driver do
|
|
|
211
197
|
driver.save_session
|
|
212
198
|
end
|
|
213
199
|
|
|
200
|
+
it 'verifies that monkey_test_precondition works fine' do
|
|
201
|
+
driver.monkey_test_precondition
|
|
202
|
+
app_info = driver.list_apps.detect { |app| app['bundle_id'] == bundle_id }
|
|
203
|
+
app_is_running = app_info && app_info['process_state'] == 'Running'
|
|
204
|
+
expect(app_is_running).to be(true)
|
|
205
|
+
expect(driver.instance_variable_get(:@running_apps)).not_to be_nil
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'verifies that monkey_test works fine' do
|
|
209
|
+
params = { udid: udid, bundle_id: bundle_id, duration: 1, session_path: Dir.pwd }
|
|
210
|
+
driver = described_class.new(params)
|
|
211
|
+
driver.monkey_test(Xcmonkey.new(params).gestures)
|
|
212
|
+
expect(driver.instance_variable_get(:@session)[:actions]).not_to be_empty
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'verifies that repeat_monkey_test works fine' do
|
|
216
|
+
session_actions = [
|
|
217
|
+
{ 'type' => 'tap', 'x' => 10, 'y' => 10 },
|
|
218
|
+
{ 'type' => 'press', 'x' => 11, 'y' => 11, 'duration' => 1.4 },
|
|
219
|
+
{ 'type' => 'swipe', 'x' => 12, 'y' => 12, 'endX' => 15, 'endY' => 15, 'duration' => 0.3 }
|
|
220
|
+
]
|
|
221
|
+
driver = described_class.new(udid: udid, bundle_id: bundle_id, session_actions: session_actions)
|
|
222
|
+
expect(driver).to receive(:tap).with(coordinates: { x: 10, y: 10 })
|
|
223
|
+
expect(driver).to receive(:press).with(coordinates: { x: 11, y: 11 }, duration: 1.4)
|
|
224
|
+
expect(driver).to receive(:swipe).with(start_coordinates: { x: 12, y: 12 }, end_coordinates: { x: 15, y: 15 }, duration: 0.3)
|
|
225
|
+
driver.repeat_monkey_test
|
|
226
|
+
expect(driver.instance_variable_get(:@session)[:actions]).to be_empty
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'verifies that unknown actions does not break repeat_monkey_test' do
|
|
230
|
+
driver = described_class.new(udid: udid, bundle_id: bundle_id, session_actions: [{ 'type' => 'test', 'x' => 10, 'y' => 10 }])
|
|
231
|
+
expect(driver).to receive(:monkey_test_precondition)
|
|
232
|
+
expect(driver).not_to receive(:tap)
|
|
233
|
+
expect(driver).not_to receive(:press)
|
|
234
|
+
expect(driver).not_to receive(:swipe)
|
|
235
|
+
driver.repeat_monkey_test
|
|
236
|
+
expect(driver.instance_variable_get(:@session)[:actions]).to be_empty
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'verifies that running apps are tracked' do
|
|
240
|
+
new_app_bundle_id = 'com.apple.Preferences'
|
|
241
|
+
driver.terminate_app(new_app_bundle_id)
|
|
242
|
+
driver.monkey_test_precondition
|
|
243
|
+
driver.launch_app(target_bundle_id: new_app_bundle_id, wait_for_state_update: true)
|
|
244
|
+
expect(driver).to receive(:launch_app).with(target_bundle_id: bundle_id)
|
|
245
|
+
expect(driver).to receive(:terminate_app).with(new_app_bundle_id)
|
|
246
|
+
driver.track_running_apps
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it 'verifies that running apps can be determined' do
|
|
250
|
+
driver.terminate_app(bundle_id)
|
|
251
|
+
sum = driver.list_running_apps.size
|
|
252
|
+
driver.launch_app(target_bundle_id: bundle_id)
|
|
253
|
+
expect(driver.list_running_apps.size).to eq(sum + 1)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
it 'verifies that app state change can be determined' do
|
|
257
|
+
driver.launch_app(target_bundle_id: bundle_id)
|
|
258
|
+
allow(driver).to receive(:detect_app_in_background).and_return(true)
|
|
259
|
+
expect(driver).not_to receive(:save_session)
|
|
260
|
+
expect(driver).to receive(:launch_app)
|
|
261
|
+
expect { driver.detect_app_state_change }.not_to raise_error
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'verifies that background is the invalid app state' do
|
|
265
|
+
driver.terminate_app(bundle_id)
|
|
266
|
+
expect(driver).to receive(:save_session)
|
|
267
|
+
expect { driver.detect_app_state_change }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'verifies that foreground is the valid app state' do
|
|
271
|
+
driver.launch_app(target_bundle_id: bundle_id, wait_for_state_update: true)
|
|
272
|
+
expect { driver.detect_app_state_change }.not_to raise_error
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
it 'verifies that background state can be determined' do
|
|
276
|
+
driver.terminate_app(bundle_id)
|
|
277
|
+
expect(driver.detect_app_in_background).to be(true)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it 'verifies that foregroung state can be determined' do
|
|
281
|
+
driver.monkey_test_precondition
|
|
282
|
+
expect(driver.detect_app_in_background).to be(false)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'verifies that xcmonkey behaves as expected on real devices' do
|
|
286
|
+
udid = '1234-5678'
|
|
287
|
+
driver = described_class.new(udid: udid, bundle_id: bundle_id)
|
|
288
|
+
allow(driver).to receive(:list_targets).and_return([{ 'udid' => udid, 'type' => 'device' }])
|
|
289
|
+
expect { driver.ensure_device_exists }.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
|
|
290
|
+
end
|
|
291
|
+
|
|
214
292
|
it 'verifies that simulator was not booted' do
|
|
215
293
|
driver.shutdown_simulator
|
|
216
294
|
error_message = "Failed to boot #{udid}"
|
data/spec/repeater_spec.rb
CHANGED
|
@@ -7,7 +7,6 @@ describe Repeater do
|
|
|
7
7
|
let(:session_file_content_without_bundle_id) { '{ "params": {"udid": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
|
|
8
8
|
let(:session_file_content_without_udid) { '{ "params": {"bundle_id": "0", "enable_simulator_keyboard": true}, "actions": [{ "type": "tap", "x": 0, "y": 0 }] }' }
|
|
9
9
|
|
|
10
|
-
# TESTME
|
|
11
10
|
it 'verifies that session cannot be validated without params' do
|
|
12
11
|
allow(File).to receive(:exist?).and_return(true)
|
|
13
12
|
allow(File).to receive(:read).and_return(session_file_content_without_params)
|
data/spec/xcmonkey_spec.rb
CHANGED
|
@@ -1,58 +1,56 @@
|
|
|
1
1
|
describe Xcmonkey do
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
expect(Xcmonkey::GEM_NAME).to eq('xcmonkey')
|
|
56
|
-
end
|
|
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' }
|
|
4
|
+
|
|
5
|
+
before do
|
|
6
|
+
allow(Logger).to receive(:info)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'verifies gestures' do
|
|
10
|
+
gestures = described_class.new(params).gestures
|
|
11
|
+
taps = [:precise_tap, :blind_tap] * 10
|
|
12
|
+
swipes = [:precise_swipe, :blind_swipe] * 5
|
|
13
|
+
presses = [:precise_press, :blind_press]
|
|
14
|
+
expect(gestures) =~ presses + taps + swipes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'verifies required params' do
|
|
18
|
+
expect(Logger).not_to receive(:error)
|
|
19
|
+
described_class.new(params)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'verifies `udid` param is required' do
|
|
23
|
+
params[:udid] = nil
|
|
24
|
+
expect(Logger).to receive(:error).with('UDID should be provided')
|
|
25
|
+
described_class.new(params)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'verifies `bundle_id` param is required' do
|
|
29
|
+
params[:bundle_id] = nil
|
|
30
|
+
expect(Logger).to receive(:error).with('Bundle identifier should be provided')
|
|
31
|
+
described_class.new(params)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'verifies `duration` param is optional' do
|
|
35
|
+
params[:duration] = nil
|
|
36
|
+
expect(Logger).not_to receive(:error)
|
|
37
|
+
described_class.new(params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'verifies `duration` param cannot be equal to zero' do
|
|
41
|
+
params[:duration] = 0
|
|
42
|
+
expect(Logger).to receive(:error).with(duration_error_msg)
|
|
43
|
+
described_class.new(params)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'verifies `duration` param cannot be negative' do
|
|
47
|
+
params[:duration] = -1
|
|
48
|
+
expect(Logger).to receive(:error).with(duration_error_msg)
|
|
49
|
+
described_class.new(params)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'verifies version' do
|
|
53
|
+
current_version = Gem::Version.new(Xcmonkey::VERSION)
|
|
54
|
+
expect(current_version).to be > Gem::Version.new('0.1.0')
|
|
57
55
|
end
|
|
58
56
|
end
|
data/xcmonkey.gemspec
CHANGED
|
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
|
3
3
|
require "xcmonkey/version"
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = "xcmonkey"
|
|
7
7
|
spec.version = Xcmonkey::VERSION
|
|
8
8
|
spec.authors = ["alteral"]
|
|
9
9
|
spec.email = ["a.alterpesotskiy@mail.ru"]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xcmonkey
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.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-
|
|
11
|
+
date: 2023-01-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -216,9 +216,9 @@ extra_rdoc_files: []
|
|
|
216
216
|
files:
|
|
217
217
|
- ".fasterer.yml"
|
|
218
218
|
- ".github/FUNDING.yml"
|
|
219
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
|
220
|
+
- ".github/ISSUE_TEMPLATE/feature-request.md"
|
|
219
221
|
- ".github/dependabot.yml"
|
|
220
|
-
- ".github/issue_template/bug_report.md"
|
|
221
|
-
- ".github/issue_template/feature_request.md"
|
|
222
222
|
- ".github/pull_request_template.md"
|
|
223
223
|
- ".github/workflows/test.yml"
|
|
224
224
|
- ".gitignore"
|