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
Returns a new instance of 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
109 110 111 112 |
# File 'lib/xcmonkey/driver.rb', line 109 def boot_simulator `idb boot #{udid}` Logger.error("Failed to boot #{udid}") if device_info['state'] != 'Booted' end |
#central_coordinates(element) ⇒ Object
186 187 188 189 190 191 192 193 194 |
# File 'lib/xcmonkey/driver.rb', line 186 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
268 269 270 |
# File 'lib/xcmonkey/driver.rb', line 268 def check_speed_limit sleep(throttle / 1000.0) if throttle.to_i > 0 end |
#checkup(counter) ⇒ Object
82 83 84 85 86 87 88 |
# File 'lib/xcmonkey/driver.rb', line 82 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
118 119 120 121 122 |
# File 'lib/xcmonkey/driver.rb', line 118 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
94 95 96 97 98 |
# File 'lib/xcmonkey/driver.rb', line 94 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
90 91 92 |
# File 'lib/xcmonkey/driver.rb', line 90 def describe_ui JSON.parse(`idb ui describe-all --udid #{udid}`) end |
#detect_app_in_background ⇒ Object
263 264 265 266 |
# File 'lib/xcmonkey/driver.rb', line 263 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
250 251 252 253 254 255 256 257 258 259 260 261 |
# File 'lib/xcmonkey/driver.rb', line 250 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
203 204 205 206 |
# File 'lib/xcmonkey/driver.rb', line 203 def device_info @device_info ||= JSON.parse(`idb describe --udid #{udid} --json`) @device_info end |
#ensure_app_installed ⇒ Object
137 138 139 140 141 |
# File 'lib/xcmonkey/driver.rb', line 137 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
143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/xcmonkey/driver.rb', line 143 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
100 101 102 103 |
# File 'lib/xcmonkey/driver.rb', line 100 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
129 130 131 |
# File 'lib/xcmonkey/driver.rb', line 129 def list_apps `idb list-apps --udid #{udid} --json`.split("\n").map! { |app| JSON.parse(app) } end |
#list_running_apps ⇒ Object
133 134 135 |
# File 'lib/xcmonkey/driver.rb', line 133 def list_running_apps list_apps.select { |app| app['process_state'] == 'Running' } end |
#list_targets ⇒ Object
124 125 126 127 |
# File 'lib/xcmonkey/driver.rb', line 124 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 |
# 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 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
163 164 165 166 167 |
# File 'lib/xcmonkey/driver.rb', line 163 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
220 221 222 |
# File 'lib/xcmonkey/driver.rb', line 220 def press_duration rand(0.5..1.5).ceil(1) end |
#random_coordinates ⇒ Object
196 197 198 199 200 201 |
# File 'lib/xcmonkey/driver.rb', line 196 def random_coordinates { x: rand(0..screen_size[:width].to_i), y: rand(0..screen_size[:height].to_i) } end |
#repeat_monkey_test ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/xcmonkey/driver.rb', line 61 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 end |
#save_session ⇒ Object
224 225 226 227 228 |
# File 'lib/xcmonkey/driver.rb', line 224 def save_session return if session_path.nil? File.write("#{session_path}/xcmonkey-session.json", JSON.pretty_generate(@session)) end |
#screen_size ⇒ Object
208 209 210 211 212 213 214 |
# File 'lib/xcmonkey/driver.rb', line 208 def screen_size screen_dimensions = device_info['screen_dimensions'] { width: screen_dimensions['width_points'], height: screen_dimensions['height_points'] } end |
#shutdown_simulator ⇒ Object
114 115 116 |
# File 'lib/xcmonkey/driver.rb', line 114 def shutdown_simulator `idb shutdown #{udid}` end |
#swipe(start_coordinates:, end_coordinates:, duration:) ⇒ Object
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/xcmonkey/driver.rb', line 169 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
216 217 218 |
# File 'lib/xcmonkey/driver.rb', line 216 def swipe_duration rand(0.1..0.7).ceil(1) end |
#tap(coordinates:) ⇒ Object
157 158 159 160 161 |
# File 'lib/xcmonkey/driver.rb', line 157 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
105 106 107 |
# File 'lib/xcmonkey/driver.rb', line 105 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
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/xcmonkey/driver.rb', line 231 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 |