run_loop 2.1.7 → 2.1.8
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/lib/run_loop/core_simulator.rb +76 -3
- data/lib/run_loop/device_agent/Frameworks.zip +0 -0
- data/lib/run_loop/device_agent/app/DeviceAgent-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/bin/CLI.json +125 -0
- data/lib/run_loop/device_agent/bin/iOSDeviceManager +0 -0
- data/lib/run_loop/device_agent/client.rb +853 -0
- data/lib/run_loop/device_agent/ios_device_manager.rb +97 -32
- data/lib/run_loop/device_agent/ipa/DeviceAgent-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/{launcher.rb → launcher_strategy.rb} +8 -8
- data/lib/run_loop/device_agent/{cbxrunner.rb → runner.rb} +15 -25
- data/lib/run_loop/device_agent/xcodebuild.rb +25 -8
- data/lib/run_loop/environment.rb +10 -6
- data/lib/run_loop/http/retriable_client.rb +7 -1
- data/lib/run_loop/simctl.rb +24 -15
- data/lib/run_loop/version.rb +7 -2
- data/lib/run_loop/xcode.rb +2 -2
- data/lib/run_loop.rb +13 -13
- metadata +10 -7
- data/lib/run_loop/device_agent/app/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/ipa/CBX-Runner.app.zip +0 -0
- data/lib/run_loop/xcuitest.rb +0 -727
data/lib/run_loop/xcuitest.rb
DELETED
@@ -1,727 +0,0 @@
|
|
1
|
-
module RunLoop
|
2
|
-
|
3
|
-
# @!visibility private
|
4
|
-
class XCUITest
|
5
|
-
|
6
|
-
require "run_loop/shell"
|
7
|
-
include RunLoop::Shell
|
8
|
-
|
9
|
-
require "run_loop/encoding"
|
10
|
-
include RunLoop::Encoding
|
11
|
-
|
12
|
-
require "run_loop/cache"
|
13
|
-
|
14
|
-
class HTTPError < RuntimeError; end
|
15
|
-
|
16
|
-
# @!visibility private
|
17
|
-
#
|
18
|
-
# These defaults may change at any time.
|
19
|
-
DEFAULTS = {
|
20
|
-
:port => 27753,
|
21
|
-
:simulator_ip => "127.0.0.1",
|
22
|
-
:http_timeout => RunLoop::Environment.ci? ? 120 : 60,
|
23
|
-
:route_version => "1.0",
|
24
|
-
:shutdown_device_agent_before_launch => false
|
25
|
-
}
|
26
|
-
|
27
|
-
# @!visibility private
|
28
|
-
def self.run(options={})
|
29
|
-
# logger = options[:logger]
|
30
|
-
simctl = options[:sim_control] || options[:simctl] || RunLoop::Simctl.new
|
31
|
-
xcode = options[:xcode] || RunLoop::Xcode.new
|
32
|
-
instruments = options[:instruments] || RunLoop::Instruments.new
|
33
|
-
|
34
|
-
# Find the Device under test, the App under test, and reset options.
|
35
|
-
device = RunLoop::Device.detect_device(options, xcode, simctl, instruments)
|
36
|
-
app_details = RunLoop::DetectAUT.detect_app_under_test(options)
|
37
|
-
reset_options = RunLoop::Core.send(:detect_reset_options, options)
|
38
|
-
|
39
|
-
app = app_details[:app]
|
40
|
-
bundle_id = app_details[:bundle_id]
|
41
|
-
|
42
|
-
if device.simulator? && app
|
43
|
-
core_sim = RunLoop::CoreSimulator.new(device, app, :xcode => xcode)
|
44
|
-
if reset_options
|
45
|
-
core_sim.reset_app_sandbox
|
46
|
-
end
|
47
|
-
|
48
|
-
simctl.ensure_software_keyboard(device)
|
49
|
-
core_sim.install
|
50
|
-
end
|
51
|
-
|
52
|
-
cbx_launcher = XCUITest.detect_cbx_launcher(options, device)
|
53
|
-
|
54
|
-
xcuitest = RunLoop::XCUITest.new(bundle_id, device, cbx_launcher)
|
55
|
-
xcuitest.launch(options)
|
56
|
-
|
57
|
-
if !RunLoop::Environment.xtc?
|
58
|
-
cache = {
|
59
|
-
:cbx_launcher => cbx_launcher.name,
|
60
|
-
:udid => device.udid,
|
61
|
-
:app => bundle_id,
|
62
|
-
:gesture_performer => :device_agent
|
63
|
-
}
|
64
|
-
RunLoop::Cache.default.write(cache)
|
65
|
-
end
|
66
|
-
xcuitest
|
67
|
-
end
|
68
|
-
|
69
|
-
# @!visibility private
|
70
|
-
#
|
71
|
-
# @param [RunLoop::Device] device the device under test
|
72
|
-
def self.default_cbx_launcher(device)
|
73
|
-
RunLoop::DeviceAgent::IOSDeviceManager.new(device)
|
74
|
-
end
|
75
|
-
|
76
|
-
# @!visibility private
|
77
|
-
# @param [Hash] options the options passed by the user
|
78
|
-
# @param [RunLoop::Device] device the device under test
|
79
|
-
def self.detect_cbx_launcher(options, device)
|
80
|
-
value = options[:cbx_launcher]
|
81
|
-
if value
|
82
|
-
if value == :xcodebuild
|
83
|
-
RunLoop::DeviceAgent::Xcodebuild.new(device)
|
84
|
-
elsif value == :ios_device_manager
|
85
|
-
RunLoop::DeviceAgent::IOSDeviceManager.new(device)
|
86
|
-
else
|
87
|
-
raise(ArgumentError,
|
88
|
-
"Expected :cbx_launcher => #{value} to be :xcodebuild or :ios_device_manager")
|
89
|
-
end
|
90
|
-
else
|
91
|
-
XCUITest.default_cbx_launcher(device)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
attr_reader :bundle_id, :device, :cbx_launcher
|
96
|
-
|
97
|
-
# @!visibility private
|
98
|
-
#
|
99
|
-
# The app with `bundle_id` needs to be installed.
|
100
|
-
#
|
101
|
-
# @param [String] bundle_id The identifier of the app under test.
|
102
|
-
# @param [RunLoop::Device] device The device under test.
|
103
|
-
# @param [RunLoop::DeviceAgent::Launcher] cbx_launcher The entity that
|
104
|
-
# launches the CBXRunner.
|
105
|
-
def initialize(bundle_id, device, cbx_launcher)
|
106
|
-
@bundle_id = bundle_id
|
107
|
-
@device = device
|
108
|
-
@cbx_launcher = cbx_launcher
|
109
|
-
end
|
110
|
-
|
111
|
-
# @!visibility private
|
112
|
-
def to_s
|
113
|
-
"#<XCUITest #{url} : #{bundle_id} : #{device} : #{cbx_launcher}>"
|
114
|
-
end
|
115
|
-
|
116
|
-
# @!visibility private
|
117
|
-
def inspect
|
118
|
-
to_s
|
119
|
-
end
|
120
|
-
|
121
|
-
# @!visibility private
|
122
|
-
def launch(options={})
|
123
|
-
start = Time.now
|
124
|
-
launch_cbx_runner(options)
|
125
|
-
launch_aut
|
126
|
-
elapsed = Time.now - start
|
127
|
-
RunLoop.log_debug("Took #{elapsed} seconds to launch #{bundle_id} on #{device}")
|
128
|
-
true
|
129
|
-
end
|
130
|
-
|
131
|
-
# @!visibility private
|
132
|
-
def running?
|
133
|
-
begin
|
134
|
-
health(ping_options)
|
135
|
-
rescue => _
|
136
|
-
nil
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# @!visibility private
|
141
|
-
def stop
|
142
|
-
begin
|
143
|
-
shutdown
|
144
|
-
rescue => _
|
145
|
-
nil
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# @!visibility private
|
150
|
-
def launch_other_app(bundle_id)
|
151
|
-
launch_aut(bundle_id)
|
152
|
-
end
|
153
|
-
|
154
|
-
# @!visibility private
|
155
|
-
def device_info
|
156
|
-
options = http_options
|
157
|
-
request = request("device")
|
158
|
-
client = client(options)
|
159
|
-
response = client.get(request)
|
160
|
-
expect_200_response(response)
|
161
|
-
end
|
162
|
-
|
163
|
-
# TODO Legacy API; remove once this branch is merged:
|
164
|
-
# https://github.com/calabash/DeviceAgent.iOS/pull/133
|
165
|
-
alias_method :runtime, :device_info
|
166
|
-
|
167
|
-
# @!visibility private
|
168
|
-
def server_pid
|
169
|
-
options = http_options
|
170
|
-
request = request("pid")
|
171
|
-
client = client(options)
|
172
|
-
response = client.get(request)
|
173
|
-
expect_200_response(response)
|
174
|
-
end
|
175
|
-
|
176
|
-
# @!visibility private
|
177
|
-
def server_version
|
178
|
-
options = http_options
|
179
|
-
request = request("version")
|
180
|
-
client = client(options)
|
181
|
-
response = client.get(request)
|
182
|
-
expect_200_response(response)
|
183
|
-
end
|
184
|
-
|
185
|
-
# @!visibility private
|
186
|
-
def session_identifier
|
187
|
-
options = http_options
|
188
|
-
request = request("sessionIdentifier")
|
189
|
-
client = client(options)
|
190
|
-
response = client.get(request)
|
191
|
-
expect_200_response(response)
|
192
|
-
end
|
193
|
-
|
194
|
-
# @!visibility private
|
195
|
-
def tree
|
196
|
-
options = http_options
|
197
|
-
request = request("tree")
|
198
|
-
client = client(options)
|
199
|
-
response = client.get(request)
|
200
|
-
expect_200_response(response)
|
201
|
-
end
|
202
|
-
|
203
|
-
# @!visibility private
|
204
|
-
def keyboard_visible?
|
205
|
-
options = http_options
|
206
|
-
parameters = { :type => "Keyboard" }
|
207
|
-
request = request("query", parameters)
|
208
|
-
client = client(options)
|
209
|
-
response = client.post(request)
|
210
|
-
hash = expect_200_response(response)
|
211
|
-
result = hash["result"]
|
212
|
-
result.count != 0
|
213
|
-
end
|
214
|
-
|
215
|
-
# @!visibility private
|
216
|
-
def enter_text(string)
|
217
|
-
if !keyboard_visible?
|
218
|
-
raise RuntimeError, "Keyboard must be visible"
|
219
|
-
end
|
220
|
-
options = http_options
|
221
|
-
parameters = {
|
222
|
-
:gesture => "enter_text",
|
223
|
-
:options => {
|
224
|
-
:string => string
|
225
|
-
}
|
226
|
-
}
|
227
|
-
request = request("gesture", parameters)
|
228
|
-
client = client(options)
|
229
|
-
response = client.post(request)
|
230
|
-
expect_200_response(response)
|
231
|
-
end
|
232
|
-
|
233
|
-
# @!visibility private
|
234
|
-
def query(mark)
|
235
|
-
options = http_options
|
236
|
-
parameters = { :id => mark }
|
237
|
-
request = request("query", parameters)
|
238
|
-
client = client(options)
|
239
|
-
response = client.post(request)
|
240
|
-
expect_200_response(response)
|
241
|
-
end
|
242
|
-
|
243
|
-
# @!visibility private
|
244
|
-
def query_for_coordinate(mark)
|
245
|
-
body = query(mark)
|
246
|
-
coordinate_from_query_result(body)
|
247
|
-
end
|
248
|
-
|
249
|
-
# @!visibility private
|
250
|
-
def touch(mark, options={})
|
251
|
-
coordinate = query_for_coordinate(mark)
|
252
|
-
perform_coordinate_gesture("touch",
|
253
|
-
coordinate[:x], coordinate[:y],
|
254
|
-
options)
|
255
|
-
end
|
256
|
-
|
257
|
-
alias_method :tap, :touch
|
258
|
-
|
259
|
-
# @!visibility private
|
260
|
-
def double_tap(mark, options={})
|
261
|
-
coordinate = query_for_coordinate(mark)
|
262
|
-
perform_coordinate_gesture("double_tap",
|
263
|
-
coordinate[:x], coordinate[:y],
|
264
|
-
options)
|
265
|
-
end
|
266
|
-
|
267
|
-
# @!visibility private
|
268
|
-
def two_finger_tap(mark, options={})
|
269
|
-
coordinate = query_for_coordinate(mark)
|
270
|
-
perform_coordinate_gesture("two_finger_tap",
|
271
|
-
coordinate[:x], coordinate[:y],
|
272
|
-
options)
|
273
|
-
end
|
274
|
-
|
275
|
-
# @!visibility private
|
276
|
-
def rotate_home_button_to(position, sleep_for=1.0)
|
277
|
-
orientation = normalize_orientation_position(position)
|
278
|
-
parameters = {
|
279
|
-
:orientation => orientation
|
280
|
-
}
|
281
|
-
request = request("rotate_home_button_to", parameters)
|
282
|
-
client = client(http_options)
|
283
|
-
response = client.post(request)
|
284
|
-
json = expect_200_response(response)
|
285
|
-
sleep(sleep_for)
|
286
|
-
json
|
287
|
-
end
|
288
|
-
|
289
|
-
# @!visibility private
|
290
|
-
def pan_between_coordinates(start_point, end_point, options={})
|
291
|
-
default_options = {
|
292
|
-
:num_fingers => 1,
|
293
|
-
:duration => 0.5
|
294
|
-
}
|
295
|
-
|
296
|
-
merged_options = default_options.merge(options)
|
297
|
-
|
298
|
-
parameters = {
|
299
|
-
:gesture => "drag",
|
300
|
-
:specifiers => {
|
301
|
-
:coordinates => [start_point, end_point]
|
302
|
-
},
|
303
|
-
:options => merged_options
|
304
|
-
}
|
305
|
-
|
306
|
-
make_gesture_request(parameters)
|
307
|
-
end
|
308
|
-
|
309
|
-
# @!visibility private
|
310
|
-
def perform_coordinate_gesture(gesture, x, y, options={})
|
311
|
-
parameters = {
|
312
|
-
:gesture => gesture,
|
313
|
-
:specifiers => {
|
314
|
-
:coordinate => {x: x, y: y}
|
315
|
-
},
|
316
|
-
:options => options
|
317
|
-
}
|
318
|
-
|
319
|
-
make_gesture_request(parameters)
|
320
|
-
end
|
321
|
-
|
322
|
-
# @!visibility private
|
323
|
-
def make_gesture_request(parameters)
|
324
|
-
|
325
|
-
RunLoop.log_debug(%Q[
|
326
|
-
Sending request to perform '#{parameters[:gesture]}' with:
|
327
|
-
|
328
|
-
#{JSON.pretty_generate(parameters)}
|
329
|
-
|
330
|
-
])
|
331
|
-
request = request("gesture", parameters)
|
332
|
-
client = client(http_options)
|
333
|
-
response = client.post(request)
|
334
|
-
expect_200_response(response)
|
335
|
-
end
|
336
|
-
|
337
|
-
# @!visibility private
|
338
|
-
def coordinate_from_query_result(hash)
|
339
|
-
matches = hash["result"]
|
340
|
-
|
341
|
-
if matches.nil? || matches.empty?
|
342
|
-
raise "Expected #{hash} to contain some results"
|
343
|
-
end
|
344
|
-
|
345
|
-
rect = matches.first["rect"]
|
346
|
-
h = rect["height"]
|
347
|
-
w = rect["width"]
|
348
|
-
x = rect["x"]
|
349
|
-
y = rect["y"]
|
350
|
-
|
351
|
-
touchx = x + (w/2.0)
|
352
|
-
touchy = y + (h/2.0)
|
353
|
-
|
354
|
-
new_rect = rect.dup
|
355
|
-
new_rect[:center_x] = touchx
|
356
|
-
new_rect[:center_y] = touchy
|
357
|
-
|
358
|
-
RunLoop.log_debug(%Q[Rect from query:
|
359
|
-
|
360
|
-
#{JSON.pretty_generate(new_rect)}
|
361
|
-
|
362
|
-
])
|
363
|
-
{:x => touchx,
|
364
|
-
:y => touchy}
|
365
|
-
end
|
366
|
-
|
367
|
-
|
368
|
-
# @!visibility private
|
369
|
-
def change_volume(up_or_down)
|
370
|
-
string = up_or_down.to_s
|
371
|
-
parameters = {
|
372
|
-
:volume => string
|
373
|
-
}
|
374
|
-
request = request("volume", parameters)
|
375
|
-
client = client(http_options)
|
376
|
-
response = client.post(request)
|
377
|
-
json = expect_200_response(response)
|
378
|
-
# Set in the route
|
379
|
-
sleep(0.2)
|
380
|
-
json
|
381
|
-
end
|
382
|
-
|
383
|
-
private
|
384
|
-
|
385
|
-
# @!visibility private
|
386
|
-
def xcrun
|
387
|
-
RunLoop::Xcrun.new
|
388
|
-
end
|
389
|
-
|
390
|
-
# @!visibility private
|
391
|
-
def url
|
392
|
-
@url ||= detect_device_agent_url
|
393
|
-
end
|
394
|
-
|
395
|
-
# @!visibility private
|
396
|
-
def detect_device_agent_url
|
397
|
-
url_from_environment ||
|
398
|
-
url_for_simulator ||
|
399
|
-
url_from_device_endpoint ||
|
400
|
-
url_from_device_name
|
401
|
-
end
|
402
|
-
|
403
|
-
# @!visibility private
|
404
|
-
def url_from_environment
|
405
|
-
url = RunLoop::Environment.device_agent_url
|
406
|
-
return if url.nil?
|
407
|
-
|
408
|
-
if url.end_with?("/")
|
409
|
-
url
|
410
|
-
else
|
411
|
-
"#{url}/"
|
412
|
-
end
|
413
|
-
end
|
414
|
-
|
415
|
-
# @!visibility private
|
416
|
-
def url_for_simulator
|
417
|
-
if device.simulator?
|
418
|
-
"http://#{DEFAULTS[:simulator_ip]}:#{DEFAULTS[:port]}/"
|
419
|
-
else
|
420
|
-
nil
|
421
|
-
end
|
422
|
-
end
|
423
|
-
|
424
|
-
# @!visibility private
|
425
|
-
def url_from_device_endpoint
|
426
|
-
calabash_endpoint = RunLoop::Environment.device_endpoint
|
427
|
-
if calabash_endpoint
|
428
|
-
base = calabash_endpoint.split(":")[0..1].join(":")
|
429
|
-
"#{base}:#{DEFAULTS[:port]}/"
|
430
|
-
else
|
431
|
-
nil
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
# @!visibility private
|
436
|
-
# TODO This block is not well tested
|
437
|
-
# TODO extract to a module; Calabash can use to detect device endpoint
|
438
|
-
def url_from_device_name
|
439
|
-
# Transforms the default "Joshua's iPhone" to a DNS name.
|
440
|
-
device_name = device.name.gsub(/[']/, "").gsub(/[\s]/, "-")
|
441
|
-
|
442
|
-
# Replace diacritic markers and unknown characters.
|
443
|
-
transliterated = transliterate(device_name).tr("?", "")
|
444
|
-
|
445
|
-
# Anything that cannot be transliterated is a ?
|
446
|
-
replaced = transliterated.tr("?", "")
|
447
|
-
|
448
|
-
"http://#{replaced}.local:#{DEFAULTS[:port]}/"
|
449
|
-
end
|
450
|
-
|
451
|
-
# @!visibility private
|
452
|
-
def server
|
453
|
-
@server ||= RunLoop::HTTP::Server.new(url)
|
454
|
-
end
|
455
|
-
|
456
|
-
# @!visibility private
|
457
|
-
def client(options={})
|
458
|
-
RunLoop::HTTP::RetriableClient.new(server, options)
|
459
|
-
end
|
460
|
-
|
461
|
-
# @!visibility private
|
462
|
-
def versioned_route(route)
|
463
|
-
"#{DEFAULTS[:route_version]}/#{route}"
|
464
|
-
end
|
465
|
-
|
466
|
-
# @!visibility private
|
467
|
-
def request(route, parameters={})
|
468
|
-
versioned = versioned_route(route)
|
469
|
-
RunLoop::HTTP::Request.request(versioned, parameters)
|
470
|
-
end
|
471
|
-
|
472
|
-
# @!visibility private
|
473
|
-
def ping_options
|
474
|
-
@ping_options ||= { :timeout => 0.5, :retries => 1 }
|
475
|
-
end
|
476
|
-
|
477
|
-
# @!visibility private
|
478
|
-
def http_options
|
479
|
-
if cbx_launcher.name == :xcodebuild
|
480
|
-
timeout = DEFAULTS[:http_timeout] * 2
|
481
|
-
{
|
482
|
-
:timeout => timeout,
|
483
|
-
:interval => 0.1,
|
484
|
-
:retries => (timeout/0.1).to_i
|
485
|
-
}
|
486
|
-
else
|
487
|
-
{
|
488
|
-
:timeout => DEFAULTS[:http_timeout],
|
489
|
-
:interval => 0.1,
|
490
|
-
:retries => (DEFAULTS[:http_timeout]/0.1).to_i
|
491
|
-
}
|
492
|
-
end
|
493
|
-
end
|
494
|
-
|
495
|
-
# @!visibility private
|
496
|
-
def session_delete
|
497
|
-
# https://xamarin.atlassian.net/browse/TCFW-255
|
498
|
-
# httpclient is unable to send a valid DELETE
|
499
|
-
args = ["curl", "-X", "DELETE", %Q[#{url}#{versioned_route("session")}]]
|
500
|
-
run_shell_command(args, {:log_cmd => true})
|
501
|
-
|
502
|
-
# options = ping_options
|
503
|
-
# request = request("session")
|
504
|
-
# client = client(options)
|
505
|
-
# begin
|
506
|
-
# response = client.delete(request)
|
507
|
-
# body = expect_200_response(response)
|
508
|
-
# RunLoop.log_debug("CBX-Runner says, #{body}")
|
509
|
-
# body
|
510
|
-
# rescue => e
|
511
|
-
# RunLoop.log_debug("CBX-Runner session delete error: #{e}")
|
512
|
-
# nil
|
513
|
-
# end
|
514
|
-
end
|
515
|
-
|
516
|
-
# @!visibility private
|
517
|
-
# TODO expect 200 response and parse body (atm the body in not valid JSON)
|
518
|
-
def shutdown
|
519
|
-
session_delete
|
520
|
-
options = ping_options
|
521
|
-
request = request("shutdown")
|
522
|
-
client = client(options)
|
523
|
-
begin
|
524
|
-
response = client.post(request)
|
525
|
-
body = response.body
|
526
|
-
RunLoop.log_debug("CBX-Runner says, \"#{body}\"")
|
527
|
-
body
|
528
|
-
rescue => e
|
529
|
-
RunLoop.log_debug("CBX-Runner shutdown error: #{e}")
|
530
|
-
nil
|
531
|
-
end
|
532
|
-
end
|
533
|
-
|
534
|
-
# @!visibility private
|
535
|
-
# TODO expect 200 response and parse body (atm the body is not valid JSON)
|
536
|
-
def health(options={})
|
537
|
-
merged_options = http_options.merge(options)
|
538
|
-
request = request("health")
|
539
|
-
client = client(merged_options)
|
540
|
-
response = client.get(request)
|
541
|
-
body = response.body
|
542
|
-
RunLoop.log_debug("CBX-Runner driver says, \"#{body}\"")
|
543
|
-
body
|
544
|
-
end
|
545
|
-
|
546
|
-
|
547
|
-
def cbx_runner_stale?
|
548
|
-
if cbx_launcher.name == :xcodebuild
|
549
|
-
return false
|
550
|
-
end
|
551
|
-
|
552
|
-
version_info = server_version
|
553
|
-
running_bundle_version = RunLoop::Version.new(version_info[:bundle_version])
|
554
|
-
bundle_version = RunLoop::App.new(cbx_launcher.runner.runner).bundle_version
|
555
|
-
|
556
|
-
running_bundle_version < bundle_version
|
557
|
-
end
|
558
|
-
|
559
|
-
# @!visibility private
|
560
|
-
def launch_cbx_runner(options={})
|
561
|
-
merged_options = DEFAULTS.merge(options)
|
562
|
-
|
563
|
-
if merged_options[:shutdown_device_agent_before_launch]
|
564
|
-
RunLoop.log_debug("Launch options insist that the DeviceAgent be shutdown")
|
565
|
-
shutdown
|
566
|
-
|
567
|
-
if cbx_launcher.name == :xcodebuild
|
568
|
-
sleep(5.0)
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
if running?
|
573
|
-
RunLoop.log_debug("DeviceAgent is already running")
|
574
|
-
if cbx_runner_stale?
|
575
|
-
shutdown
|
576
|
-
else
|
577
|
-
# TODO: is it necessary to return the pid? Or can we return true?
|
578
|
-
return server_pid
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
|
-
if cbx_launcher.name == :xcodebuild
|
583
|
-
RunLoop.log_debug("xcodebuild is the launcher - terminating existing xcodebuild processes")
|
584
|
-
term_options = { :timeout => 0.5 }
|
585
|
-
kill_options = { :timeout => 0.5 }
|
586
|
-
RunLoop::ProcessWaiter.new("xcodebuild").pids.each do |pid|
|
587
|
-
term = RunLoop::ProcessTerminator.new(pid, 'TERM', "xcodebuild", term_options)
|
588
|
-
killed = term.kill_process
|
589
|
-
unless killed
|
590
|
-
RunLoop::ProcessTerminator.new(pid, 'KILL', "xcodebuild", kill_options)
|
591
|
-
end
|
592
|
-
end
|
593
|
-
sleep(2.0)
|
594
|
-
end
|
595
|
-
|
596
|
-
start = Time.now
|
597
|
-
RunLoop.log_debug("Waiting for CBX-Runner to launch...")
|
598
|
-
pid = cbx_launcher.launch
|
599
|
-
|
600
|
-
if cbx_launcher.name == :xcodebuild
|
601
|
-
sleep(2.0)
|
602
|
-
end
|
603
|
-
|
604
|
-
begin
|
605
|
-
health
|
606
|
-
rescue RunLoop::HTTP::Error => _
|
607
|
-
raise %Q[
|
608
|
-
|
609
|
-
Could not connect to the DeviceAgent service.
|
610
|
-
|
611
|
-
device: #{device}
|
612
|
-
url: #{url}
|
613
|
-
|
614
|
-
To diagnose the problem tail the launcher log file:
|
615
|
-
|
616
|
-
$ tail -1000 -F #{cbx_launcher.class.log_file}
|
617
|
-
|
618
|
-
]
|
619
|
-
end
|
620
|
-
|
621
|
-
RunLoop.log_debug("Took #{Time.now - start} launch and respond to /health")
|
622
|
-
|
623
|
-
# TODO: is it necessary to return the pid? Or can we return true?
|
624
|
-
pid
|
625
|
-
end
|
626
|
-
|
627
|
-
# @!visibility private
|
628
|
-
def launch_aut(bundle_id = @bundle_id)
|
629
|
-
client = client(http_options)
|
630
|
-
request = request("session", {:bundleID => bundle_id})
|
631
|
-
|
632
|
-
begin
|
633
|
-
response = client.post(request)
|
634
|
-
RunLoop.log_debug("Launched #{bundle_id} on #{device}")
|
635
|
-
RunLoop.log_debug("#{response.body}")
|
636
|
-
if device.simulator?
|
637
|
-
# It is not clear yet whether we should do this. There is a problem
|
638
|
-
# in the simulator_wait_for_stable_state; it waits too long.
|
639
|
-
# device.simulator_wait_for_stable_state
|
640
|
-
end
|
641
|
-
expect_200_response(response)
|
642
|
-
rescue => e
|
643
|
-
raise e.class, %Q[Could not launch #{bundle_id} on #{device}:
|
644
|
-
|
645
|
-
#{e.message}
|
646
|
-
|
647
|
-
Something went wrong.
|
648
|
-
]
|
649
|
-
end
|
650
|
-
end
|
651
|
-
|
652
|
-
# @!visibility private
|
653
|
-
def response_body_to_hash(response)
|
654
|
-
body = response.body
|
655
|
-
begin
|
656
|
-
JSON.parse(body)
|
657
|
-
rescue TypeError, JSON::ParserError => _
|
658
|
-
raise RunLoop::XCUITest::HTTPError,
|
659
|
-
"Could not parse response '#{body}'; the app has probably crashed"
|
660
|
-
end
|
661
|
-
end
|
662
|
-
|
663
|
-
# @!visibility private
|
664
|
-
def expect_200_response(response)
|
665
|
-
body = response_body_to_hash(response)
|
666
|
-
if response.status_code < 300 && !body["error"]
|
667
|
-
return body
|
668
|
-
end
|
669
|
-
|
670
|
-
if response.status_code > 300
|
671
|
-
raise RunLoop::XCUITest::HTTPError,
|
672
|
-
%Q[Expected status code < 300, found #{response.status_code}.
|
673
|
-
|
674
|
-
Server replied with:
|
675
|
-
|
676
|
-
#{body}
|
677
|
-
|
678
|
-
]
|
679
|
-
else
|
680
|
-
raise RunLoop::XCUITest::HTTPError,
|
681
|
-
%Q[Expected JSON response with no error, but found
|
682
|
-
|
683
|
-
#{body["error"]}
|
684
|
-
|
685
|
-
]
|
686
|
-
|
687
|
-
end
|
688
|
-
end
|
689
|
-
|
690
|
-
# @!visibility private
|
691
|
-
def normalize_orientation_position(position)
|
692
|
-
if position.is_a?(Symbol)
|
693
|
-
orientation_for_position_symbol(position)
|
694
|
-
elsif position.is_a?(Fixnum)
|
695
|
-
position
|
696
|
-
else
|
697
|
-
raise ArgumentError, %Q[
|
698
|
-
Expected #{position} to be a Symbol or Fixnum but found #{position.class}
|
699
|
-
|
700
|
-
]
|
701
|
-
end
|
702
|
-
end
|
703
|
-
|
704
|
-
# @!visibility private
|
705
|
-
def orientation_for_position_symbol(position)
|
706
|
-
symbol = position.to_sym
|
707
|
-
|
708
|
-
case symbol
|
709
|
-
when :down, :bottom
|
710
|
-
return 1
|
711
|
-
when :up, :top
|
712
|
-
return 2
|
713
|
-
when :right
|
714
|
-
return 3
|
715
|
-
when :left
|
716
|
-
return 4
|
717
|
-
else
|
718
|
-
raise ArgumentError, %Q[
|
719
|
-
Could not coerce '#{position}' into a valid orientation.
|
720
|
-
|
721
|
-
Valid values are: :down, :up, :right, :left, :bottom, :top
|
722
|
-
]
|
723
|
-
end
|
724
|
-
end
|
725
|
-
end
|
726
|
-
end
|
727
|
-
|