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.
-
#enable_simulator_keyboard ⇒ Object
Returns the value of attribute enable_simulator_keyboard.
-
#session_actions ⇒ Object
Returns the value of attribute session_actions.
-
#session_duration ⇒ Object
Returns the value of attribute session_duration.
-
#session_path ⇒ Object
Returns the value of attribute session_path.
-
#udid ⇒ Object
Returns the value of attribute udid.
Instance Method Summary collapse
- #boot_simulator ⇒ Object
- #central_coordinates(element) ⇒ 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 |
# File 'lib/xcmonkey/driver.rb', line 4 def initialize(params) self.udid = params[:udid] self.bundle_id = params[:bundle_id] self.session_duration = params[:duration] self.session_path = params[:session_path] self.enable_simulator_keyboard = params[:enable_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 |
#enable_simulator_keyboard ⇒ Object
Returns the value of attribute enable_simulator_keyboard.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def enable_simulator_keyboard @enable_simulator_keyboard 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_duration ⇒ Object
Returns the value of attribute session_duration.
2 3 4 |
# File 'lib/xcmonkey/driver.rb', line 2 def session_duration @session_duration 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 |
#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
107 108 109 110 |
# File 'lib/xcmonkey/driver.rb', line 107 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 ? rand(0..screen_size[:width].to_i) : x, y: y > screen_size[:height].to_i ? rand(0..screen_size[:height].to_i) : y } end |
#configure_simulator_keyboard ⇒ Object
116 117 118 119 120 |
# File 'lib/xcmonkey/driver.rb', line 116 def configure_simulator_keyboard shutdown_simulator keyboard_status = enable_simulator_keyboard ? 0 : 1 `defaults write com.apple.iphonesimulator ConnectHardwareKeyboard #{keyboard_status}` end |
#describe_point(x, y) ⇒ Object
92 93 94 95 96 |
# File 'lib/xcmonkey/driver.rb', line 92 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
88 89 90 |
# File 'lib/xcmonkey/driver.rb', line 88 def describe_ui JSON.parse(`idb ui describe-all --udid #{udid}`) end |
#detect_app_in_background ⇒ Object
260 261 262 263 |
# File 'lib/xcmonkey/driver.rb', line 260 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
247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/xcmonkey/driver.rb', line 247 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 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
135 136 137 138 139 |
# File 'lib/xcmonkey/driver.rb', line 135 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
141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/xcmonkey/driver.rb', line 141 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
98 99 100 101 |
# File 'lib/xcmonkey/driver.rb', line 98 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
127 128 129 |
# File 'lib/xcmonkey/driver.rb', line 127 def list_apps `idb list-apps --udid #{udid} --json`.split("\n").map! { |app| JSON.parse(app) } end |
#list_running_apps ⇒ Object
131 132 133 |
# File 'lib/xcmonkey/driver.rb', line 131 def list_running_apps list_apps.select { |app| app['process_state'] == 'Running' } end |
#list_targets ⇒ Object
122 123 124 125 |
# File 'lib/xcmonkey/driver.rb', line 122 def list_targets @targets ||= `idb list-targets --json`.split("\n").map! { |target| JSON.parse(target) } @targets end |
#monkey_test(gestures) ⇒ Object
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 56 57 58 59 60 61 62 |
# File 'lib/xcmonkey/driver.rb', line 24 def monkey_test(gestures) monkey_test_precondition app_elements = describe_ui.shuffle current_time = Time.now counter = 0 while Time.now < current_time + session_duration 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 detect_app_state_change track_running_apps if counter % 5 == 0 # Track running apps after every 5th action to speed up the test counter += 1 app_elements = describe_ui.shuffle end save_session end |
#monkey_test_precondition ⇒ Object
15 16 17 18 19 20 21 22 |
# File 'lib/xcmonkey/driver.rb', line 15 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
161 162 163 164 165 |
# File 'lib/xcmonkey/driver.rb', line 161 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
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/xcmonkey/driver.rb', line 64 def repeat_monkey_test monkey_test_precondition counter = 0 session_actions.each do |action| 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 detect_app_state_change track_running_apps if counter % 5 == 0 counter += 1 end end |
#save_session ⇒ Object
224 225 226 |
# File 'lib/xcmonkey/driver.rb', line 224 def save_session 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
112 113 114 |
# File 'lib/xcmonkey/driver.rb', line 112 def shutdown_simulator `idb shutdown #{udid}` end |
#swipe(start_coordinates:, end_coordinates:, duration:) ⇒ Object
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/xcmonkey/driver.rb', line 167 def swipe(start_coordinates:, end_coordinates:, duration:) Logger.info( "Swipe (#{duration}s):", payload: "#{JSON.pretty_generate(start_coordinates)} => #{JSON.pretty_generate(end_coordinates)}" ) 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
155 156 157 158 159 |
# File 'lib/xcmonkey/driver.rb', line 155 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
103 104 105 |
# File 'lib/xcmonkey/driver.rb', line 103 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
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/xcmonkey/driver.rb', line 229 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 |