Class: Driver
- Inherits:
-
Object
- Object
- Driver
- Defined in:
- lib/xcmonkey/driver.rb
Instance Attribute Summary collapse
-
#bundle_id ⇒ Object
Returns the value of attribute bundle_id.
-
#disable_simulator_keyboard ⇒ Object
Returns the value of attribute disable_simulator_keyboard.
-
#event_count ⇒ Object
Returns the value of attribute event_count.
-
#ignore_crashes ⇒ Object
Returns the value of attribute ignore_crashes.
-
#session_actions ⇒ Object
Returns the value of attribute session_actions.
-
#session_path ⇒ Object
Returns the value of attribute session_path.
-
#throttle ⇒ Object
Returns the value of attribute throttle.
-
#udid ⇒ Object
Returns the value of attribute udid.
Instance Method Summary collapse
- #boot_simulator ⇒ Object
- #central_coordinates(element) ⇒ Object
- #check_speed_limit ⇒ Object
- #checkup(counter) ⇒ Object
- #configure_simulator_keyboard ⇒ Object
- #describe_point(x, y) ⇒ Object
- #describe_ui ⇒ Object
- #detect_app_in_background ⇒ Object
-
#detect_app_state_change ⇒ Object
This function takes ≈300ms.
- #device_info ⇒ Object
- #ensure_app_installed ⇒ Object
- #ensure_device_exists ⇒ Object
-
#initialize(params) ⇒ Driver
constructor
A new instance of Driver.
- #launch_app(target_bundle_id:, wait_for_state_update: false) ⇒ Object
- #list_apps ⇒ Object
- #list_running_apps ⇒ Object
- #list_targets ⇒ Object
- #monkey_test(gestures) ⇒ Object
- #monkey_test_precondition ⇒ Object
- #press(coordinates:, duration:) ⇒ Object
- #press_duration ⇒ Object
- #random_coordinates ⇒ Object
- #repeat_monkey_test ⇒ Object
- #save_session ⇒ Object
- #screen_size ⇒ Object
- #shutdown_simulator ⇒ Object
- #swipe(start_coordinates:, end_coordinates:, duration:) ⇒ Object
- #swipe_duration ⇒ Object
- #tap(coordinates:) ⇒ Object
- #terminate_app(target_bundle_id) ⇒ Object
-
#track_running_apps ⇒ Object
This function takes ≈200ms.
Constructor Details
#initialize(params) ⇒ Driver
4 5 6 7 8 9 10 11 12 13 14 15 |
# File 'lib/xcmonkey/driver.rb', line 4 def initialize(params) self.udid = params[:udid] self.throttle = params[:throttle] self.bundle_id = params[:bundle_id] self.event_count = params[:event_count] self.session_path = params[:session_path] self.ignore_crashes = params[:ignore_crashes] self.disable_simulator_keyboard = params[:disable_simulator_keyboard] self.session_actions = params[:session_actions] @session = { params: params, actions: [] } ensure_driver_installed end |
Instance Attribute Details
#bundle_id ⇒ Object
Returns the value of attribute bundle_id.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def bundle_id @bundle_id end |
#disable_simulator_keyboard ⇒ Object
Returns the value of attribute disable_simulator_keyboard.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def disable_simulator_keyboard @disable_simulator_keyboard end |
#event_count ⇒ Object
Returns the value of attribute event_count.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def event_count @event_count end |
#ignore_crashes ⇒ Object
Returns the value of attribute ignore_crashes.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def ignore_crashes @ignore_crashes end |
#session_actions ⇒ Object
Returns the value of attribute session_actions.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def session_actions @session_actions end |
#session_path ⇒ Object
Returns the value of attribute session_path.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def session_path @session_path end |
#throttle ⇒ Object
Returns the value of attribute throttle.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def throttle @throttle end |
#udid ⇒ Object
Returns the value of attribute udid.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def udid @udid end |
Instance Method Details
#boot_simulator ⇒ Object
111 112 113 114 |
# File 'lib/xcmonkey/driver.rb', line 111 def boot_simulator `idb boot #{udid}` Logger.error("Failed to boot #{udid}") if device_info['state'] != 'Booted' end |
#central_coordinates(element) ⇒ Object
188 189 190 191 192 193 194 195 196 |
# File 'lib/xcmonkey/driver.rb', line 188 def central_coordinates(element) frame = element['frame'] x = (frame['x'] + (frame['width'] / 2)).abs.to_i y = (frame['y'] + (frame['height'] / 2)).abs.to_i { x: x > screen_size[:width].to_i ? random_coordinates[:x] : x, y: y > screen_size[:height].to_i ? random_coordinates[:y] : y } end |
#check_speed_limit ⇒ Object
270 271 272 |
# File 'lib/xcmonkey/driver.rb', line 270 def check_speed_limit sleep(throttle / 1000.0) if throttle.to_i > 0 end |
#checkup(counter) ⇒ Object
84 85 86 87 88 89 90 |
# File 'lib/xcmonkey/driver.rb', line 84 def checkup(counter) detect_app_state_change if counter % 5 == 0 || throttle.to_i > 0 # Track running apps after every 5th action track_running_apps # (unless `throttle` was provided) to speed up the test end check_speed_limit end |
#configure_simulator_keyboard ⇒ Object
120 121 122 123 124 |
# File 'lib/xcmonkey/driver.rb', line 120 def configure_simulator_keyboard shutdown_simulator keyboard_status = disable_simulator_keyboard ? 1 : 0 `defaults write com.apple.iphonesimulator ConnectHardwareKeyboard #{keyboard_status}` end |
#describe_point(x, y) ⇒ Object
96 97 98 99 100 |
# File 'lib/xcmonkey/driver.rb', line 96 def describe_point(x, y) point_info = JSON.parse(`idb ui describe-point --udid #{udid} #{x} #{y}`) Logger.info("x:#{x} y:#{y} point info:", payload: JSON.pretty_generate(point_info)) point_info end |
#describe_ui ⇒ Object
92 93 94 |
# File 'lib/xcmonkey/driver.rb', line 92 def describe_ui JSON.parse(`idb ui describe-all --udid #{udid}`) end |
#detect_app_in_background ⇒ Object
265 266 267 268 |
# File 'lib/xcmonkey/driver.rb', line 265 def detect_app_in_background current_app_label = describe_ui.detect { |el| el['type'] == 'Application' }['AXLabel'] current_app_label.nil? || current_app_label.strip.empty? end |
#detect_app_state_change ⇒ Object
This function takes ≈300ms
252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/xcmonkey/driver.rb', line 252 def detect_app_state_change return unless detect_app_in_background target_app_is_running = list_running_apps.any? { |app| app['bundle_id'] == bundle_id } if target_app_is_running || ignore_crashes launch_app(target_bundle_id: bundle_id) else save_session Logger.error("Target app has crashed or been terminated") end end |
#device_info ⇒ Object
205 206 207 208 |
# File 'lib/xcmonkey/driver.rb', line 205 def device_info @device_info ||= JSON.parse(`idb describe --udid #{udid} --json`) @device_info end |
#ensure_app_installed ⇒ Object
139 140 141 142 143 |
# File 'lib/xcmonkey/driver.rb', line 139 def ensure_app_installed return if list_apps.any? { |app| app['bundle_id'] == bundle_id } Logger.error("App #{bundle_id} is not installed on device #{udid}") end |
#ensure_device_exists ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/xcmonkey/driver.rb', line 145 def ensure_device_exists device = list_targets.detect { |target| target['udid'] == udid } Logger.error("Can't find device #{udid}") if device.nil? Logger.info('Device info:', payload: JSON.pretty_generate(device)) if device['type'] == 'simulator' configure_simulator_keyboard boot_simulator else Logger.error('xcmonkey does not support real devices yet. ' \ 'For more information see https://github.com/alteral/xcmonkey/issues/7') end end |
#launch_app(target_bundle_id:, wait_for_state_update: false) ⇒ Object
102 103 104 105 |
# File 'lib/xcmonkey/driver.rb', line 102 def launch_app(target_bundle_id:, wait_for_state_update: false) `idb launch --udid #{udid} #{target_bundle_id}` wait_until_app_launched(target_bundle_id) if wait_for_state_update end |
#list_apps ⇒ Object
131 132 133 |
# File 'lib/xcmonkey/driver.rb', line 131 def list_apps `idb list-apps --udid #{udid} --json`.split("\n").map! { |app| JSON.parse(app) } end |
#list_running_apps ⇒ Object
135 136 137 |
# File 'lib/xcmonkey/driver.rb', line 135 def list_running_apps list_apps.select { |app| app['process_state'] == 'Running' } end |
#list_targets ⇒ Object
126 127 128 129 |
# File 'lib/xcmonkey/driver.rb', line 126 def list_targets @targets ||= `idb list-targets --json`.split("\n").map! { |target| JSON.parse(target) } @targets end |
#monkey_test(gestures) ⇒ Object
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 56 57 58 59 60 |
# File 'lib/xcmonkey/driver.rb', line 26 def monkey_test(gestures) monkey_test_precondition event_count.times do |counter| app_elements = describe_ui.shuffle el1_coordinates = central_coordinates(app_elements.first) el2_coordinates = central_coordinates(app_elements.last) case gestures.sample when :precise_tap tap(coordinates: el1_coordinates) when :blind_tap tap(coordinates: random_coordinates) when :precise_press press(coordinates: el1_coordinates, duration: press_duration) when :blind_press press(coordinates: random_coordinates, duration: press_duration) when :precise_swipe swipe( start_coordinates: el1_coordinates, end_coordinates: el2_coordinates, duration: swipe_duration ) when :blind_swipe swipe( start_coordinates: random_coordinates, end_coordinates: random_coordinates, duration: swipe_duration ) else next end checkup(counter) end save_session terminate_app(bundle_id) end |
#monkey_test_precondition ⇒ Object
17 18 19 20 21 22 23 24 |
# File 'lib/xcmonkey/driver.rb', line 17 def monkey_test_precondition puts ensure_device_exists ensure_app_installed terminate_app(bundle_id) launch_app(target_bundle_id: bundle_id, wait_for_state_update: true) @running_apps = list_running_apps end |
#press(coordinates:, duration:) ⇒ Object
165 166 167 168 169 |
# File 'lib/xcmonkey/driver.rb', line 165 def press(coordinates:, duration:) Logger.info("Press (#{duration}s):", payload: JSON.pretty_generate(coordinates)) @session[:actions] << { type: :press, x: coordinates[:x], y: coordinates[:y], duration: duration } unless session_actions `idb ui tap --udid #{udid} --duration #{duration} #{coordinates[:x]} #{coordinates[:y]}` end |
#press_duration ⇒ Object
222 223 224 |
# File 'lib/xcmonkey/driver.rb', line 222 def press_duration rand(0.5..1.5).ceil(1) end |
#random_coordinates ⇒ Object
198 199 200 201 202 203 |
# File 'lib/xcmonkey/driver.rb', line 198 def random_coordinates { x: rand(0..screen_size[:width].to_i), y: rand(0..screen_size[:height].to_i) } end |
#repeat_monkey_test ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/xcmonkey/driver.rb', line 62 def repeat_monkey_test monkey_test_precondition session_actions.each_with_index do |action, counter| case action['type'] when 'tap' tap(coordinates: { x: action['x'], y: action['y'] }) when 'press' press(coordinates: { x: action['x'], y: action['y'] }, duration: action['duration']) when 'swipe' swipe( start_coordinates: { x: action['x'], y: action['y'] }, end_coordinates: { x: action['endX'], y: action['endY'] }, duration: action['duration'] ) else next end checkup(counter) end terminate_app(bundle_id) end |
#save_session ⇒ Object
226 227 228 229 230 |
# File 'lib/xcmonkey/driver.rb', line 226 def save_session return if session_path.nil? File.write("#{session_path}/xcmonkey-session.json", JSON.pretty_generate(@session)) end |
#screen_size ⇒ Object
210 211 212 213 214 215 216 |
# File 'lib/xcmonkey/driver.rb', line 210 def screen_size screen_dimensions = device_info['screen_dimensions'] { width: screen_dimensions['width_points'], height: screen_dimensions['height_points'] } end |
#shutdown_simulator ⇒ Object
116 117 118 |
# File 'lib/xcmonkey/driver.rb', line 116 def shutdown_simulator `idb shutdown #{udid}` end |
#swipe(start_coordinates:, end_coordinates:, duration:) ⇒ Object
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/xcmonkey/driver.rb', line 171 def swipe(start_coordinates:, end_coordinates:, duration:) payload = "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}" Logger.info("Swipe (#{duration}s):", payload: payload) unless session_actions @session[:actions] << { type: :swipe, x: start_coordinates[:x], y: start_coordinates[:y], endX: end_coordinates[:x], endY: end_coordinates[:y], duration: duration } end coordinates = "#{start_coordinates[:x]} #{start_coordinates[:y]} #{end_coordinates[:x]} #{end_coordinates[:y]}" `idb ui swipe --udid #{udid} --duration #{duration} #{coordinates}` end |
#swipe_duration ⇒ Object
218 219 220 |
# File 'lib/xcmonkey/driver.rb', line 218 def swipe_duration rand(0.1..0.7).ceil(1) end |
#tap(coordinates:) ⇒ Object
159 160 161 162 163 |
# File 'lib/xcmonkey/driver.rb', line 159 def tap(coordinates:) Logger.info('Tap:', payload: JSON.pretty_generate(coordinates)) @session[:actions] << { type: :tap, x: coordinates[:x], y: coordinates[:y] } unless session_actions `idb ui tap --udid #{udid} #{coordinates[:x]} #{coordinates[:y]}` end |
#terminate_app(target_bundle_id) ⇒ Object
107 108 109 |
# File 'lib/xcmonkey/driver.rb', line 107 def terminate_app(target_bundle_id) `idb terminate --udid #{udid} #{target_bundle_id} 2>/dev/null` end |
#track_running_apps ⇒ Object
This function takes ≈200ms
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/xcmonkey/driver.rb', line 233 def track_running_apps current_list_of_running_apps = list_running_apps if @running_apps != current_list_of_running_apps currently_running_bundle_ids = current_list_of_running_apps.map { |app| app['bundle_id'] } previously_running_bundle_ids = @running_apps.map { |app| app['bundle_id'] } new_apps = currently_running_bundle_ids - previously_running_bundle_ids return if new_apps.empty? launch_app(target_bundle_id: bundle_id) new_apps.each do |id| Logger.warn("Shutting down: #{id}") terminate_app(id) end end end |