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 +4 -4
- data/.github/stale.yml +18 -0
- data/CHANGELOG.md +42 -0
- data/Gemfile +14 -0
- data/README.md +23 -3
- data/bin/xcmonkey +15 -5
- data/fastlane/Fastfile +3 -3
- data/lib/xcmonkey/driver.rb +83 -42
- data/lib/xcmonkey/repeater.rb +9 -3
- data/lib/xcmonkey/version.rb +1 -1
- data/lib/xcmonkey.rb +13 -12
- data/spec/describer_spec.rb +4 -2
- data/spec/driver_spec.rb +111 -32
- data/spec/repeater_spec.rb +5 -5
- data/spec/xcmonkey_spec.rb +38 -10
- data/xcmonkey.gemspec +16 -26
- metadata +4 -170
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be0e0b66cbc58704452a6defce61d6bd9f7ba88f7489796f4b7cc6a4f4489a11
|
|
4
|
+
data.tar.gz: 8551a6fd090f89cf20c2cd2e81bc8edc4856b9eb5516a50227db772cb0f013eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 --
|
|
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
|
-
|
|
129
|
+
event_count: 100,
|
|
110
130
|
bundle_id: 'com.apple.Maps',
|
|
111
|
-
|
|
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('-
|
|
21
|
-
c.option('-
|
|
22
|
-
c.option('
|
|
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
|
-
|
|
32
|
+
event_count: options.event_count,
|
|
33
|
+
throttle: options.throttle,
|
|
28
34
|
session_path: options.session_path,
|
|
29
|
-
|
|
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:
|
|
12
|
-
tag_name:
|
|
13
|
-
description: "
|
|
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
|
)
|
data/lib/xcmonkey/driver.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
class Driver
|
|
2
|
-
attr_accessor :udid, :bundle_id, :
|
|
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.
|
|
8
|
+
self.event_count = params[:event_count]
|
|
8
9
|
self.session_path = params[:session_path]
|
|
9
|
-
self.
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
78
|
+
checkup(counter)
|
|
82
79
|
end
|
|
83
80
|
end
|
|
84
81
|
|
|
85
|
-
def
|
|
86
|
-
|
|
87
|
-
|
|
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} #{
|
|
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} #{
|
|
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 =
|
|
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
|
-
|
|
164
|
-
|
|
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 ?
|
|
187
|
-
y: y > screen_size[:height].to_i ?
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
|
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'] ==
|
|
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 #{
|
|
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
|
data/lib/xcmonkey/repeater.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Repeater
|
|
2
|
-
attr_accessor :udid, :bundle_id, :
|
|
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
|
-
|
|
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.
|
|
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
|
data/lib/xcmonkey/version.rb
CHANGED
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[:
|
|
14
|
-
params[:
|
|
15
|
-
params[:
|
|
16
|
-
|
|
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
|
|
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]
|
|
37
|
+
Logger.error('Session path should be a directory') if params[:session_path] && !File.directory?(params[:session_path])
|
|
37
38
|
|
|
38
|
-
if params[:
|
|
39
|
-
Logger.error('
|
|
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
|
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
|
@@ -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
|
|
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,
|
|
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,
|
|
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(
|
|
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,
|
|
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}"
|
data/spec/repeater_spec.rb
CHANGED
|
@@ -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", "
|
|
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", "
|
|
6
|
-
let(:session_file_content_without_actions) { '{ "params": {"udid": "0", "bundle_id": "0", "
|
|
7
|
-
let(:session_file_content_without_bundle_id) { '{ "params": {"udid": "0", "
|
|
8
|
-
let(:session_file_content_without_udid) { '{ "params": {"bundle_id": "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)
|
data/spec/xcmonkey_spec.rb
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
describe Xcmonkey do
|
|
2
|
-
let(:params) { { udid: '123', bundle_id: 'example.com.app',
|
|
3
|
-
let(:
|
|
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 `
|
|
31
|
-
params[:
|
|
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 `
|
|
37
|
-
params[:
|
|
38
|
-
expect(Logger).to receive(:error).with(
|
|
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 `
|
|
43
|
-
params[:
|
|
44
|
-
expect(Logger).to receive(:error).with(
|
|
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
|
|
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
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
8
|
+
spec.authors = ['alteral']
|
|
9
|
+
spec.email = ['a.alterpesotskiy@mail.ru']
|
|
10
10
|
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.homepage =
|
|
13
|
-
spec.license =
|
|
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[
|
|
16
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
|
17
17
|
else
|
|
18
|
-
raise
|
|
18
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
spec.bindir =
|
|
22
|
-
spec.executables = [
|
|
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
|
-
|
|
28
|
-
spec.
|
|
29
|
-
spec.
|
|
30
|
-
|
|
31
|
-
spec.
|
|
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.
|
|
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-
|
|
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
|