Skip to content

Commit a9847e1

Browse files
committed
Force keep xcmonkey in the target app (#9)
1 parent 38e0bfa commit a9847e1

File tree

6 files changed

+145
-63
lines changed

6 files changed

+145
-63
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ gem 'xcmonkey'
3939
### To run a stress test
4040

4141
```bash
42-
xcmonkey test --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA" --bundle-id "com.apple.Maps" --duration 100
42+
$ xcmonkey test --duration 100 --bundle-id "com.apple.Maps" --udid "413EA256-CFFB-4312-94A6-12592BEE4CBA"
4343

4444
12:44:19.343: Device info: {
4545
"name": "iPhone 14 Pro",
@@ -106,9 +106,9 @@ require 'xcmonkey'
106106

107107
lane :test do
108108
Xcmonkey.new(
109-
udid: '413EA256-CFFB-4312-94A6-12592BEE4CBA',
109+
duration: 100,
110110
bundle_id: 'com.apple.Maps',
111-
duration: 100
111+
udid: '413EA256-CFFB-4312-94A6-12592BEE4CBA'
112112
).run
113113
end
114114
```

lib/xcmonkey/driver.rb

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ def monkey_test_precondition
1616
puts
1717
ensure_device_exists
1818
ensure_app_installed
19-
terminate_app
20-
open_home_screen(with_tracker: true)
21-
launch_app
19+
terminate_app(bundle_id)
20+
launch_app(target_bundle_id: bundle_id, wait_for_state_update: true)
21+
@running_apps = list_running_apps
2222
end
2323

2424
def monkey_test(gestures)
2525
monkey_test_precondition
2626
app_elements = describe_ui.shuffle
2727
current_time = Time.now
28+
counter = 0
2829
while Time.now < current_time + session_duration
2930
el1_coordinates = central_coordinates(app_elements.first)
3031
el2_coordinates = central_coordinates(app_elements.last)
@@ -52,17 +53,17 @@ def monkey_test(gestures)
5253
else
5354
next
5455
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
5559
app_elements = describe_ui.shuffle
56-
next unless app_elements.include?(@home_tracker)
57-
58-
save_session
59-
Logger.error('App lost')
6060
end
6161
save_session
6262
end
6363

6464
def repeat_monkey_test
6565
monkey_test_precondition
66+
counter = 0
6667
session_actions.each do |action|
6768
case action['type']
6869
when 'tap'
@@ -78,15 +79,12 @@ def repeat_monkey_test
7879
else
7980
next
8081
end
81-
Logger.error('App lost') if describe_ui.shuffle.include?(@home_tracker)
82+
detect_app_state_change
83+
track_running_apps if counter % 5 == 0
84+
counter += 1
8285
end
8386
end
8487

85-
def open_home_screen(with_tracker: false)
86-
`idb ui button --udid #{udid} HOME`
87-
detect_home_unique_element if with_tracker
88-
end
89-
9088
def describe_ui
9189
JSON.parse(`idb ui describe-all --udid #{udid}`)
9290
end
@@ -97,13 +95,13 @@ def describe_point(x, y)
9795
point_info
9896
end
9997

100-
def launch_app
101-
`idb launch --udid #{udid} #{bundle_id}`
102-
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
103101
end
104102

105-
def terminate_app
106-
`idb terminate --udid #{udid} #{bundle_id} 2>/dev/null`
103+
def terminate_app(target_bundle_id)
104+
`idb terminate --udid #{udid} #{target_bundle_id} 2>/dev/null`
107105
end
108106

109107
def boot_simulator
@@ -130,6 +128,10 @@ def list_apps
130128
`idb list-apps --udid #{udid} --json`.split("\n").map! { |app| JSON.parse(app) }
131129
end
132130

131+
def list_running_apps
132+
list_apps.select { |app| app['process_state'] == 'Running' }
133+
end
134+
133135
def ensure_app_installed
134136
return if list_apps.any? { |app| app['bundle_id'] == bundle_id }
135137

@@ -144,6 +146,9 @@ def ensure_device_exists
144146
if device['type'] == 'simulator'
145147
configure_simulator_keyboard
146148
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')
147152
end
148153
end
149154

@@ -220,28 +225,57 @@ def save_session
220225
File.write("#{session_path}/xcmonkey-session.json", JSON.pretty_generate(@session))
221226
end
222227

223-
private
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
224235

225-
def ensure_driver_installed
226-
Logger.error("'idb' doesn't seem to be installed") if `which idb`.strip.empty?
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
227244
end
228245

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'
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")
233257
end
234-
@home_tracker
235258
end
236259

237-
def wait_until_app_launched
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)
238272
app_is_running = false
239273
current_time = Time.now
240274
while !app_is_running && Time.now < current_time + 5
241-
app_info = list_apps.detect { |app| app['bundle_id'] == bundle_id }
275+
app_info = list_apps.detect { |app| app['bundle_id'] == target_bundle_id }
242276
app_is_running = app_info && app_info['process_state'] == 'Running'
243277
end
244-
Logger.error("Can't run the app #{bundle_id}") unless app_is_running
278+
Logger.error("Can't run the app #{target_bundle_id}") unless app_is_running
245279
Logger.info('App info:', payload: JSON.pretty_generate(app_info))
246280
end
247281
end

lib/xcmonkey/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
class Xcmonkey
2-
VERSION = '1.1.0'
2+
VERSION = '1.2.0'
33
end

spec/describer_spec.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@
22
let(:udid) { `xcrun simctl list | grep " iPhone 14 Pro Max"`.split("\n")[0].split('(')[1].split(')')[0] }
33
let(:driver) { Driver.new(udid: udid) }
44

5-
it 'verifies that point can be described (integer)' do
5+
before do
66
allow(Logger).to receive(:info)
7+
end
8+
9+
it 'verifies that point can be described (integer)' do
710
driver.boot_simulator
811
point_info = described_class.new(udid: udid, x: 10, y: 10).run
912
expect(point_info).not_to be_empty
1013
end
1114

1215
it 'verifies that point can be described (string)' do
13-
allow(Logger).to receive(:info)
1416
driver.boot_simulator
1517
point_info = described_class.new(udid: udid, x: '10', y: '10').run
1618
expect(point_info).not_to be_empty

0 commit comments

Comments
 (0)