run_loop 2.1.9 → 2.1.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/run_loop/cli/simctl.rb +1 -1
- data/lib/run_loop/core_simulator.rb +9 -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/iOSDeviceManager +0 -0
- data/lib/run_loop/device_agent/client.rb +635 -187
- data/lib/run_loop/device_agent/frameworks.rb +0 -4
- data/lib/run_loop/device_agent/ios_device_manager.rb +15 -12
- data/lib/run_loop/device_agent/ipa/DeviceAgent-Runner.app.zip +0 -0
- data/lib/run_loop/device_agent/xcodebuild.rb +8 -7
- data/lib/run_loop/http/retriable_client.rb +42 -26
- data/lib/run_loop/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1d5ec2c34563d9302dd2ea711a02542f391cdb1
|
4
|
+
data.tar.gz: 0559160c9bba7c824d069f8b1f90819328033691
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 46181f7467e976ef0f53d874b5145de368055be6d3fac52e6e872c1f28a1a39f9c04de92df0b4bcbfce270ef6b1438f1c1f590246cd51bc796c32acf54ec8c9b
|
7
|
+
data.tar.gz: e11a8e89c0a0ee501a961342d6e71705a3ebc9d1788d8ee23adc2fadeb4ac1ea321e44390df8517e753b81535eefe6332084ae63de9786ea24a9e613871a38b1
|
data/lib/run_loop/cli/simctl.rb
CHANGED
@@ -342,7 +342,10 @@ class RunLoop::CoreSimulator
|
|
342
342
|
end
|
343
343
|
|
344
344
|
# Launch the simulator indicated by device.
|
345
|
-
def launch_simulator
|
345
|
+
def launch_simulator(options={})
|
346
|
+
merged_options = {
|
347
|
+
:wait_for_stable => true
|
348
|
+
}.merge(options)
|
346
349
|
|
347
350
|
if running_simulator_pid != nil
|
348
351
|
# There is a running simulator.
|
@@ -370,7 +373,9 @@ class RunLoop::CoreSimulator
|
|
370
373
|
options = { :timeout => 5, :raise_on_timeout => true }
|
371
374
|
RunLoop::ProcessWaiter.new(sim_name, options).wait_for_any
|
372
375
|
|
373
|
-
|
376
|
+
if merged_options[:wait_for_stable]
|
377
|
+
device.simulator_wait_for_stable_state
|
378
|
+
end
|
374
379
|
|
375
380
|
elapsed = Time.now - start_time
|
376
381
|
RunLoop.log_debug("Took #{elapsed} seconds to launch the simulator")
|
@@ -591,7 +596,8 @@ Command had no output
|
|
591
596
|
timeout = DEFAULT_OPTIONS[:install_app_timeout]
|
592
597
|
simctl.install(device, app, timeout)
|
593
598
|
|
594
|
-
|
599
|
+
# Experimental: don't wait after the install
|
600
|
+
# device.simulator_wait_for_stable_state
|
595
601
|
installed_app_bundle_dir
|
596
602
|
end
|
597
603
|
|
Binary file
|
Binary file
|
Binary file
|
@@ -19,14 +19,50 @@ module RunLoop
|
|
19
19
|
# @!visibility private
|
20
20
|
#
|
21
21
|
# These defaults may change at any time.
|
22
|
+
#
|
23
|
+
# You can override these values if they do not work in your environment.
|
24
|
+
#
|
25
|
+
# For cucumber users, the best place to override would be in your
|
26
|
+
# features/support/env.rb.
|
27
|
+
#
|
28
|
+
# For example:
|
29
|
+
#
|
30
|
+
# RunLoop::DeviceAgent::Client::DEFAULTS[:http_timeout] = 60
|
22
31
|
DEFAULTS = {
|
23
32
|
:port => 27753,
|
24
33
|
:simulator_ip => "127.0.0.1",
|
25
|
-
:http_timeout => RunLoop::Environment.ci? ? 120 : 10,
|
34
|
+
:http_timeout => (RunLoop::Environment.ci? || RunLoop::Environment.xtc?) ? 120 : 10,
|
26
35
|
:route_version => "1.0",
|
36
|
+
|
37
|
+
# Ignored in the XTC.
|
38
|
+
# This key is subject to removal or changes
|
39
|
+
:device_agent_install_timeout => RunLoop::Environment.ci? ? 120 : 60,
|
40
|
+
# This value must always be false on the XTC.
|
41
|
+
# This is should only be used by gem maintainers or very advanced users.
|
27
42
|
:shutdown_device_agent_before_launch => false
|
28
43
|
}
|
29
44
|
|
45
|
+
# @!visibility private
|
46
|
+
#
|
47
|
+
# These defaults may change at any time.
|
48
|
+
#
|
49
|
+
# You can override these values if they do not work in your environment.
|
50
|
+
#
|
51
|
+
# For cucumber users, the best place to override would be in your
|
52
|
+
# features/support/env.rb.
|
53
|
+
#
|
54
|
+
# For example:
|
55
|
+
#
|
56
|
+
# RunLoop::DeviceAgent::Client::WAIT_DEFAULTS[:timeout] = 30
|
57
|
+
WAIT_DEFAULTS = {
|
58
|
+
timeout: (RunLoop::Environment.ci? ||
|
59
|
+
RunLoop::Environment.xtc?) ? 30 : 15,
|
60
|
+
# This key is subject to removal or changes.
|
61
|
+
retry_frequency: 0.1,
|
62
|
+
# This key is subject to removal or changes.
|
63
|
+
exception_class: Timeout::Error
|
64
|
+
}
|
65
|
+
|
30
66
|
# @!visibility private
|
31
67
|
def self.run(options={})
|
32
68
|
# logger = options[:logger]
|
@@ -76,17 +112,30 @@ $ xcrun security find-identity -v -p codesigning
|
|
76
112
|
end
|
77
113
|
end
|
78
114
|
|
79
|
-
|
80
|
-
|
81
|
-
|
115
|
+
install_timeout = options.fetch(:device_agent_install_timeout,
|
116
|
+
DEFAULTS[:device_agent_install_timeout])
|
117
|
+
shutdown_before_launch = options.fetch(:shutdown_device_agent_before_launch,
|
118
|
+
DEFAULTS[:shutdown_device_agent_before_launch])
|
119
|
+
|
120
|
+
launcher_options = {
|
121
|
+
code_sign_identity: code_sign_identity,
|
122
|
+
device_agent_install_timeout: install_timeout,
|
123
|
+
shutdown_device_agent_before_launch: shutdown_before_launch
|
124
|
+
}
|
125
|
+
|
126
|
+
xcuitest = RunLoop::DeviceAgent::Client.new(bundle_id, device,
|
127
|
+
cbx_launcher, launcher_options)
|
128
|
+
xcuitest.launch
|
82
129
|
|
83
130
|
if !RunLoop::Environment.xtc?
|
84
131
|
cache = {
|
85
|
-
:cbx_launcher => cbx_launcher.name,
|
86
132
|
:udid => device.udid,
|
87
133
|
:app => bundle_id,
|
88
134
|
:automator => :device_agent,
|
89
|
-
:code_sign_identity => code_sign_identity
|
135
|
+
:code_sign_identity => code_sign_identity,
|
136
|
+
:launcher => cbx_launcher.name,
|
137
|
+
:launcher_pid => xcuitest.launcher_pid,
|
138
|
+
:launcher_options => launcher_options
|
90
139
|
}
|
91
140
|
RunLoop::Cache.default.write(cache)
|
92
141
|
end
|
@@ -119,7 +168,7 @@ $ xcrun security find-identity -v -p codesigning
|
|
119
168
|
end
|
120
169
|
end
|
121
170
|
|
122
|
-
attr_reader :bundle_id, :device, :cbx_launcher, :
|
171
|
+
attr_reader :bundle_id, :device, :cbx_launcher, :launcher_options, :launcher_pid
|
123
172
|
|
124
173
|
# @!visibility private
|
125
174
|
#
|
@@ -129,10 +178,16 @@ $ xcrun security find-identity -v -p codesigning
|
|
129
178
|
# @param [RunLoop::Device] device The device under test.
|
130
179
|
# @param [RunLoop::DeviceAgent::LauncherStrategy] cbx_launcher The entity that
|
131
180
|
# launches the CBXRunner.
|
132
|
-
def initialize(bundle_id, device, cbx_launcher)
|
181
|
+
def initialize(bundle_id, device, cbx_launcher, launcher_options)
|
133
182
|
@bundle_id = bundle_id
|
134
183
|
@device = device
|
135
184
|
@cbx_launcher = cbx_launcher
|
185
|
+
@launcher_options = launcher_options
|
186
|
+
|
187
|
+
if !@launcher_options[:device_agent_install_timeout]
|
188
|
+
default = DEFAULTS[:device_agent_install_timeout]
|
189
|
+
@launcher_options[:device_agent_install_timeout] = default
|
190
|
+
end
|
136
191
|
end
|
137
192
|
|
138
193
|
# @!visibility private
|
@@ -146,10 +201,9 @@ $ xcrun security find-identity -v -p codesigning
|
|
146
201
|
end
|
147
202
|
|
148
203
|
# @!visibility private
|
149
|
-
def launch
|
150
|
-
@launch_options = options
|
204
|
+
def launch
|
151
205
|
start = Time.now
|
152
|
-
launch_cbx_runner
|
206
|
+
launch_cbx_runner
|
153
207
|
launch_aut
|
154
208
|
elapsed = Time.now - start
|
155
209
|
RunLoop.log_debug("Took #{elapsed} seconds to launch #{bundle_id} on #{device}")
|
@@ -167,6 +221,11 @@ $ xcrun security find-identity -v -p codesigning
|
|
167
221
|
|
168
222
|
# @!visibility private
|
169
223
|
def stop
|
224
|
+
if RunLoop::Environment.xtc?
|
225
|
+
RunLoop.log_error("Calling shutdown on the XTC is not supported.")
|
226
|
+
return
|
227
|
+
end
|
228
|
+
|
170
229
|
begin
|
171
230
|
shutdown
|
172
231
|
rescue => _
|
@@ -175,6 +234,8 @@ $ xcrun security find-identity -v -p codesigning
|
|
175
234
|
end
|
176
235
|
|
177
236
|
# @!visibility private
|
237
|
+
#
|
238
|
+
# Experimental!
|
178
239
|
def launch_other_app(bundle_id)
|
179
240
|
launch_aut(bundle_id)
|
180
241
|
end
|
@@ -183,49 +244,31 @@ $ xcrun security find-identity -v -p codesigning
|
|
183
244
|
def device_info
|
184
245
|
options = http_options
|
185
246
|
request = request("device")
|
186
|
-
client =
|
247
|
+
client = http_client(options)
|
187
248
|
response = client.get(request)
|
188
|
-
|
249
|
+
expect_300_response(response)
|
189
250
|
end
|
190
251
|
|
191
252
|
# TODO Legacy API; remove once this branch is merged:
|
192
253
|
# https://github.com/calabash/DeviceAgent.iOS/pull/133
|
193
254
|
alias_method :runtime, :device_info
|
194
255
|
|
195
|
-
# @!visibility private
|
196
|
-
def server_pid
|
197
|
-
options = http_options
|
198
|
-
request = request("pid")
|
199
|
-
client = client(options)
|
200
|
-
response = client.get(request)
|
201
|
-
expect_200_response(response)
|
202
|
-
end
|
203
|
-
|
204
256
|
# @!visibility private
|
205
257
|
def server_version
|
206
258
|
options = http_options
|
207
259
|
request = request("version")
|
208
|
-
client =
|
260
|
+
client = http_client(options)
|
209
261
|
response = client.get(request)
|
210
|
-
|
211
|
-
end
|
212
|
-
|
213
|
-
# @!visibility private
|
214
|
-
def session_identifier
|
215
|
-
options = http_options
|
216
|
-
request = request("sessionIdentifier")
|
217
|
-
client = client(options)
|
218
|
-
response = client.get(request)
|
219
|
-
expect_200_response(response)
|
262
|
+
expect_300_response(response)
|
220
263
|
end
|
221
264
|
|
222
265
|
# @!visibility private
|
223
266
|
def tree
|
224
267
|
options = http_options
|
225
268
|
request = request("tree")
|
226
|
-
client =
|
269
|
+
client = http_client(options)
|
227
270
|
response = client.get(request)
|
228
|
-
|
271
|
+
expect_300_response(response)
|
229
272
|
end
|
230
273
|
|
231
274
|
# @!visibility private
|
@@ -233,9 +276,9 @@ $ xcrun security find-identity -v -p codesigning
|
|
233
276
|
options = http_options
|
234
277
|
parameters = { :type => "Keyboard" }
|
235
278
|
request = request("query", parameters)
|
236
|
-
client =
|
279
|
+
client = http_client(options)
|
237
280
|
response = client.post(request)
|
238
|
-
hash =
|
281
|
+
hash = expect_300_response(response)
|
239
282
|
result = hash["result"]
|
240
283
|
result.count != 0
|
241
284
|
end
|
@@ -253,22 +296,135 @@ $ xcrun security find-identity -v -p codesigning
|
|
253
296
|
}
|
254
297
|
}
|
255
298
|
request = request("gesture", parameters)
|
256
|
-
client =
|
299
|
+
client = http_client(options)
|
257
300
|
response = client.post(request)
|
258
|
-
|
301
|
+
expect_300_response(response)
|
259
302
|
end
|
260
303
|
|
261
304
|
# @!visibility private
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
305
|
+
#
|
306
|
+
# @example
|
307
|
+
# query({id: "login", :type "Button"})
|
308
|
+
#
|
309
|
+
# query({marked: "login"})
|
310
|
+
#
|
311
|
+
# query({marked: "login", type: "TextField"})
|
312
|
+
#
|
313
|
+
# query({type: "Button", index: 2})
|
314
|
+
#
|
315
|
+
# query({text: "Log in"})
|
316
|
+
#
|
317
|
+
# query({id: "hidden button", :all => true})
|
318
|
+
#
|
319
|
+
# # Escaping single quote is not necessary, but supported.
|
320
|
+
# query({text: "Karl's problem"})
|
321
|
+
# query({text: "Karl\'s problem"})
|
322
|
+
#
|
323
|
+
# # Escaping double quote is not necessary, but supported.
|
324
|
+
# query({text: "\"To know is not enough.\""})
|
325
|
+
# query({text: %Q["To know is not enough."]})
|
326
|
+
#
|
327
|
+
# Querying for text with newlines is not supported yet.
|
328
|
+
#
|
329
|
+
# The query language supports the following keys:
|
330
|
+
# * :marked - accessibilityIdentifier, accessibilityLabel, text, and value
|
331
|
+
# * :id - accessibilityIdentifier
|
332
|
+
# * :type - an XCUIElementType shorthand, e.g. XCUIElementTypeButton =>
|
333
|
+
# Button. See the link below for available types. Note, however that
|
334
|
+
# some XCUIElementTypes are not available on iOS.
|
335
|
+
# * :index - Applied after all other specifiers.
|
336
|
+
# * :all - Filter the result by visibility. Defaults to false. See the
|
337
|
+
# discussion below about visibility.
|
338
|
+
#
|
339
|
+
# ### Visibility
|
340
|
+
#
|
341
|
+
# The rules for visibility are:
|
342
|
+
#
|
343
|
+
# 1. If any part of the view is visible, the visible.
|
344
|
+
# 2. If the view has alpha 0, it is not visible.
|
345
|
+
# 3. If the view has a size (0,0) it is not visible.
|
346
|
+
# 4. If the view is not within the bounds of the screen, it is not visible.
|
347
|
+
#
|
348
|
+
# Visibility is determined using the "hitable" XCUIElement property.
|
349
|
+
# XCUITest, particularly under Xcode 7, is not consistent about setting
|
350
|
+
# the "hitable" property correctly. Views that are not "hitable" might
|
351
|
+
# respond to gestures.
|
352
|
+
#
|
353
|
+
# Regarding rule #1 - this is different from the Calabash iOS and Android
|
354
|
+
# definition of visibility which requires the mid-point of the view to be
|
355
|
+
# visible.
|
356
|
+
#
|
357
|
+
# ### Results
|
358
|
+
#
|
359
|
+
# Results are returned as an Array of Hashes.
|
360
|
+
#
|
361
|
+
# ```
|
362
|
+
# [
|
363
|
+
# {
|
364
|
+
# "enabled": true,
|
365
|
+
# "id": "mostly hidden button",
|
366
|
+
# "hitable": true,
|
367
|
+
# "rect": {
|
368
|
+
# "y": 459,
|
369
|
+
# "x": 24,
|
370
|
+
# "height": 25,
|
371
|
+
# "width": 100
|
372
|
+
# },
|
373
|
+
# "label": "Mostly Hidden",
|
374
|
+
# "type": "Button",
|
375
|
+
# "hit_point": {
|
376
|
+
# "x": 25,
|
377
|
+
# "y": 460
|
378
|
+
# },
|
379
|
+
# "test_id": 1
|
380
|
+
# }
|
381
|
+
# ]
|
382
|
+
# ```
|
383
|
+
#
|
384
|
+
# @see http://masilotti.com/xctest-documentation/Constants/XCUIElementType.html
|
385
|
+
# @param [Hash] uiquery A hash describing the query.
|
386
|
+
# @return [Array<Hash>] An array of elements matching the `uiquery`.
|
387
|
+
def query(uiquery)
|
388
|
+
merged_options = {
|
389
|
+
all: false
|
390
|
+
}.merge(uiquery)
|
391
|
+
|
392
|
+
allowed_keys = [:all, :id, :index, :marked, :text, :type]
|
393
|
+
unknown_keys = uiquery.keys - allowed_keys
|
394
|
+
if !unknown_keys.empty?
|
395
|
+
keys = allowed_keys.map { |key| ":#{key}" }.join(", ")
|
396
|
+
raise ArgumentError, %Q[
|
397
|
+
Unsupported key or keys found: '#{unknown_keys}'.
|
398
|
+
|
399
|
+
Allowed keys for a query are: #{keys}
|
400
|
+
|
401
|
+
]
|
402
|
+
end
|
403
|
+
|
404
|
+
has_any_key = (allowed_keys & uiquery.keys).any?
|
405
|
+
if !has_any_key
|
406
|
+
keys = allowed_keys.map { |key| ":#{key}" }.join(", ")
|
407
|
+
raise ArgumentError, %Q[
|
408
|
+
Query does not contain any keysUnsupported key or keys found: '#{unknown_keys}'.
|
409
|
+
|
410
|
+
Allowed keys for a query are: #{keys}
|
411
|
+
|
412
|
+
]
|
413
|
+
end
|
414
|
+
|
415
|
+
parameters = merged_options.dup.tap { |hs| hs.delete(:all) }
|
416
|
+
if parameters.empty?
|
417
|
+
keys = allowed_keys.map { |key| ":#{key}" }.join(", ")
|
418
|
+
raise ArgumentError, %Q[
|
419
|
+
Query must contain at least one of these keys:
|
420
|
+
|
421
|
+
#{keys}
|
422
|
+
|
423
|
+
]
|
424
|
+
end
|
268
425
|
|
269
|
-
parameters = { merged_options[:specifier] => mark }
|
270
426
|
request = request("query", parameters)
|
271
|
-
client =
|
427
|
+
client = http_client(http_options)
|
272
428
|
|
273
429
|
RunLoop.log_debug %Q[Sending query with parameters:
|
274
430
|
|
@@ -277,7 +433,7 @@ $ xcrun security find-identity -v -p codesigning
|
|
277
433
|
]
|
278
434
|
|
279
435
|
response = client.post(request)
|
280
|
-
hash =
|
436
|
+
hash = expect_300_response(response)
|
281
437
|
elements = hash["result"]
|
282
438
|
|
283
439
|
if merged_options[:all]
|
@@ -290,47 +446,99 @@ $ xcrun security find-identity -v -p codesigning
|
|
290
446
|
end
|
291
447
|
|
292
448
|
# @!visibility private
|
293
|
-
def
|
449
|
+
def alert
|
294
450
|
parameters = { :type => "Alert" }
|
295
451
|
request = request("query", parameters)
|
296
|
-
client =
|
452
|
+
client = http_client(http_options)
|
297
453
|
response = client.post(request)
|
298
|
-
hash =
|
299
|
-
|
454
|
+
hash = expect_300_response(response)
|
455
|
+
hash["result"]
|
300
456
|
end
|
301
457
|
|
302
458
|
# @!visibility private
|
303
|
-
def
|
304
|
-
|
305
|
-
coordinate_from_query_result(elements)
|
459
|
+
def alert_visible?
|
460
|
+
!alert.empty?
|
306
461
|
end
|
307
462
|
|
308
463
|
# @!visibility private
|
309
|
-
def
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
464
|
+
def spring_board_alert
|
465
|
+
request = request("springBoardAlert")
|
466
|
+
client = http_client(http_options)
|
467
|
+
response = client.get(request)
|
468
|
+
hash = expect_300_response(response)
|
469
|
+
hash["result"]
|
314
470
|
end
|
315
471
|
|
316
|
-
|
472
|
+
# @!visibility private
|
473
|
+
def spring_board_alert_visible?
|
474
|
+
!spring_board_alert.empty?
|
475
|
+
end
|
317
476
|
|
318
477
|
# @!visibility private
|
319
|
-
|
320
|
-
|
478
|
+
# @see #query
|
479
|
+
def query_for_coordinate(uiquery)
|
480
|
+
element = wait_for_view(uiquery)
|
481
|
+
coordinate_from_query_result([element])
|
482
|
+
end
|
483
|
+
|
484
|
+
# @!visibility private
|
485
|
+
#
|
486
|
+
# :num_fingers
|
487
|
+
# :duration
|
488
|
+
# :repetitions
|
489
|
+
# @see #query
|
490
|
+
def touch(uiquery, options={})
|
491
|
+
coordinate = query_for_coordinate(uiquery)
|
492
|
+
perform_coordinate_gesture("touch", coordinate[:x], coordinate[:y], options)
|
493
|
+
end
|
494
|
+
|
495
|
+
# @!visibility private
|
496
|
+
# @see #touch
|
497
|
+
def touch_coordinate(coordinate, options={})
|
498
|
+
x = coordinate[:x] || coordinate["x"]
|
499
|
+
y = coordinate[:y] || coordinate["y"]
|
500
|
+
touch_point(x, y, options)
|
501
|
+
end
|
502
|
+
|
503
|
+
# @!visibility private
|
504
|
+
# @see #touch
|
505
|
+
def touch_point(x, y, options={})
|
506
|
+
perform_coordinate_gesture("touch", x, y, options)
|
507
|
+
end
|
508
|
+
|
509
|
+
# @!visibility private
|
510
|
+
# @see #touch
|
511
|
+
# @see #query
|
512
|
+
def double_tap(uiquery, options={})
|
513
|
+
coordinate = query_for_coordinate(uiquery)
|
321
514
|
perform_coordinate_gesture("double_tap",
|
322
515
|
coordinate[:x], coordinate[:y],
|
323
516
|
options)
|
324
517
|
end
|
325
518
|
|
326
519
|
# @!visibility private
|
327
|
-
|
328
|
-
|
520
|
+
# @see #touch
|
521
|
+
# @see #query
|
522
|
+
def two_finger_tap(uiquery, options={})
|
523
|
+
coordinate = query_for_coordinate(uiquery)
|
329
524
|
perform_coordinate_gesture("two_finger_tap",
|
330
525
|
coordinate[:x], coordinate[:y],
|
331
526
|
options)
|
332
527
|
end
|
333
528
|
|
529
|
+
# @!visibility private
|
530
|
+
# @see #touch
|
531
|
+
# @see #query
|
532
|
+
def long_press(uiquery, options={})
|
533
|
+
merged_options = {
|
534
|
+
:duration => 1.1
|
535
|
+
}.merge(options)
|
536
|
+
|
537
|
+
coordinate = query_for_coordinate(uiquery)
|
538
|
+
perform_coordinate_gesture("touch", coordinate[:x], coordinate[:y],
|
539
|
+
merged_options)
|
540
|
+
end
|
541
|
+
|
334
542
|
# @!visibility private
|
335
543
|
def rotate_home_button_to(position, sleep_for=1.0)
|
336
544
|
orientation = normalize_orientation_position(position)
|
@@ -338,9 +546,9 @@ $ xcrun security find-identity -v -p codesigning
|
|
338
546
|
:orientation => orientation
|
339
547
|
}
|
340
548
|
request = request("rotate_home_button_to", parameters)
|
341
|
-
client =
|
549
|
+
client = http_client(http_options)
|
342
550
|
response = client.post(request)
|
343
|
-
json =
|
551
|
+
json = expect_300_response(response)
|
344
552
|
sleep(sleep_for)
|
345
553
|
json
|
346
554
|
end
|
@@ -387,9 +595,9 @@ $ xcrun security find-identity -v -p codesigning
|
|
387
595
|
|
388
596
|
]
|
389
597
|
request = request("gesture", parameters)
|
390
|
-
client =
|
598
|
+
client = http_client(http_options)
|
391
599
|
response = client.post(request)
|
392
|
-
|
600
|
+
expect_300_response(response)
|
393
601
|
end
|
394
602
|
|
395
603
|
# @!visibility private
|
@@ -416,12 +624,11 @@ $ xcrun security find-identity -v -p codesigning
|
|
416
624
|
|
417
625
|
#{JSON.pretty_generate(new_rect)}
|
418
626
|
|
419
|
-
|
627
|
+
])
|
420
628
|
{:x => touchx,
|
421
629
|
:y => touchy}
|
422
630
|
end
|
423
631
|
|
424
|
-
|
425
632
|
# @!visibility private
|
426
633
|
def change_volume(up_or_down)
|
427
634
|
string = up_or_down.to_s
|
@@ -429,16 +636,217 @@ $ xcrun security find-identity -v -p codesigning
|
|
429
636
|
:volume => string
|
430
637
|
}
|
431
638
|
request = request("volume", parameters)
|
432
|
-
client =
|
639
|
+
client = http_client(http_options)
|
433
640
|
response = client.post(request)
|
434
|
-
json =
|
641
|
+
json = expect_300_response(response)
|
435
642
|
# Set in the route
|
436
643
|
sleep(0.2)
|
437
644
|
json
|
438
645
|
end
|
439
646
|
|
647
|
+
# TODO: animation model
|
648
|
+
def wait_for_animations
|
649
|
+
sleep(0.5)
|
650
|
+
end
|
651
|
+
|
652
|
+
# @!visibility private
|
653
|
+
def wait_for(timeout_message, options={}, &block)
|
654
|
+
wait_options = WAIT_DEFAULTS.merge(options)
|
655
|
+
timeout = wait_options[:timeout]
|
656
|
+
exception_class = wait_options[:exception_class]
|
657
|
+
with_timeout(timeout, timeout_message, exception_class) do
|
658
|
+
loop do
|
659
|
+
value = block.call
|
660
|
+
return value if value
|
661
|
+
sleep(wait_options[:retry_frequency])
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
# @!visibility private
|
667
|
+
def wait_for_keyboard(timeout=WAIT_DEFAULTS[:timeout])
|
668
|
+
options = WAIT_DEFAULTS.dup
|
669
|
+
options[:timeout] = timeout
|
670
|
+
message = %Q[
|
671
|
+
|
672
|
+
Timed out after #{timeout} seconds waiting for the keyboard to appear.
|
673
|
+
|
674
|
+
]
|
675
|
+
wait_for(message, options) do
|
676
|
+
keyboard_visible?
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
# @!visibility private
|
681
|
+
def wait_for_alert(timeout=WAIT_DEFAULTS[:timeout])
|
682
|
+
options = WAIT_DEFAULTS.dup
|
683
|
+
options[:timeout] = timeout
|
684
|
+
message = %Q[
|
685
|
+
|
686
|
+
Timed out after #{timeout} seconds waiting for an alert to appear.
|
687
|
+
|
688
|
+
]
|
689
|
+
wait_for(message, options) do
|
690
|
+
alert_visible?
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
# @!visibility private
|
695
|
+
def wait_for_no_alert(timeout=WAIT_DEFAULTS[:timeout])
|
696
|
+
options = WAIT_DEFAULTS.dup
|
697
|
+
options[:timeout] = timeout
|
698
|
+
message = %Q[
|
699
|
+
|
700
|
+
Timed out after #{timeout} seconds waiting for an alert to disappear.
|
701
|
+
|
702
|
+
]
|
703
|
+
|
704
|
+
wait_for(message, options) do
|
705
|
+
!alert_visible?
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
# @!visibility private
|
710
|
+
def wait_for_text_in_view(text, uiquery, options={})
|
711
|
+
merged_options = WAIT_DEFAULTS.merge(options)
|
712
|
+
result = wait_for_view(uiquery, merged_options)
|
713
|
+
|
714
|
+
candidates = [result["value"],
|
715
|
+
result["label"]]
|
716
|
+
match = candidates.any? do |elm|
|
717
|
+
elm == text
|
718
|
+
end
|
719
|
+
if !match
|
720
|
+
fail(%Q[
|
721
|
+
|
722
|
+
Expected to find '#{text}' as a 'value' or 'label' in
|
723
|
+
|
724
|
+
#{JSON.pretty_generate(result)}
|
725
|
+
|
726
|
+
])
|
727
|
+
end
|
728
|
+
end
|
729
|
+
|
730
|
+
# @!visibility private
|
731
|
+
def wait_for_view(uiquery, options={})
|
732
|
+
merged_options = WAIT_DEFAULTS.merge(options)
|
733
|
+
|
734
|
+
unless merged_options[:message]
|
735
|
+
message = %Q[
|
736
|
+
|
737
|
+
Waited #{merged_options[:timeout]} seconds for
|
738
|
+
|
739
|
+
#{uiquery}
|
740
|
+
|
741
|
+
to match a view.
|
742
|
+
|
743
|
+
]
|
744
|
+
merged_options[:timeout_message] = message
|
745
|
+
end
|
746
|
+
|
747
|
+
result = nil
|
748
|
+
wait_for(merged_options[:timeout_message], options) do
|
749
|
+
result = query(uiquery)
|
750
|
+
!result.empty?
|
751
|
+
end
|
752
|
+
|
753
|
+
result[0]
|
754
|
+
end
|
755
|
+
|
756
|
+
# @!visibility private
|
757
|
+
def wait_for_no_view(uiquery, options={})
|
758
|
+
merged_options = WAIT_DEFAULTS.merge(options)
|
759
|
+
unless merged_options[:message]
|
760
|
+
message = %Q[
|
761
|
+
|
762
|
+
Waited #{merged_options[:timeout]} seconds for
|
763
|
+
|
764
|
+
#{uiquery}
|
765
|
+
|
766
|
+
to match no views.
|
767
|
+
|
768
|
+
]
|
769
|
+
merged_options[:timeout_message] = message
|
770
|
+
end
|
771
|
+
|
772
|
+
result = nil
|
773
|
+
wait_for(merged_options[:timeout_message], options) do
|
774
|
+
result = query(uiquery)
|
775
|
+
result.empty?
|
776
|
+
end
|
777
|
+
|
778
|
+
result[0]
|
779
|
+
end
|
780
|
+
|
781
|
+
# @!visibility private
|
782
|
+
class PrivateWaitTimeoutError < RuntimeError ; end
|
783
|
+
|
784
|
+
# @!visibility private
|
785
|
+
def with_timeout(timeout, timeout_message,
|
786
|
+
exception_class=WAIT_DEFAULTS[:exception_class], &block)
|
787
|
+
if timeout_message.nil? ||
|
788
|
+
(timeout_message.is_a?(String) && timeout_message.empty?)
|
789
|
+
raise ArgumentError, 'You must provide a timeout message'
|
790
|
+
end
|
791
|
+
|
792
|
+
unless block_given?
|
793
|
+
raise ArgumentError, 'You must provide a block'
|
794
|
+
end
|
795
|
+
|
796
|
+
# Timeout.timeout will never timeout if the given `timeout` is zero.
|
797
|
+
# We will raise an exception if the timeout is zero.
|
798
|
+
# Timeout.timeout already raises an exception if `timeout` is negative.
|
799
|
+
if timeout == 0
|
800
|
+
raise ArgumentError, 'Timeout cannot be 0'
|
801
|
+
end
|
802
|
+
|
803
|
+
message = if timeout_message.is_a?(Proc)
|
804
|
+
timeout_message.call({timeout: timeout})
|
805
|
+
else
|
806
|
+
timeout_message
|
807
|
+
end
|
808
|
+
|
809
|
+
failed = false
|
810
|
+
|
811
|
+
begin
|
812
|
+
Timeout.timeout(timeout, PrivateWaitTimeoutError) do
|
813
|
+
return block.call
|
814
|
+
end
|
815
|
+
rescue PrivateWaitTimeoutError => _
|
816
|
+
# If we raise Timeout here the stack trace will be cluttered and we
|
817
|
+
# wish to show the user a clear message, avoiding
|
818
|
+
# "`rescue in with_timeout':" in the stack trace.
|
819
|
+
failed = true
|
820
|
+
end
|
821
|
+
|
822
|
+
if failed
|
823
|
+
fail(exception_class, message)
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
# @!visibility private
|
828
|
+
def fail(*several_variants)
|
829
|
+
arg0 = several_variants[0]
|
830
|
+
arg1 = several_variants[1]
|
831
|
+
|
832
|
+
if arg1.nil?
|
833
|
+
exception_type = RuntimeError
|
834
|
+
message = arg0
|
835
|
+
else
|
836
|
+
exception_type = arg0
|
837
|
+
message = arg1
|
838
|
+
end
|
839
|
+
|
840
|
+
raise exception_type, message
|
841
|
+
end
|
842
|
+
|
843
|
+
=begin
|
844
|
+
PRIVATE
|
845
|
+
=end
|
440
846
|
private
|
441
847
|
|
848
|
+
attr_reader :http_client
|
849
|
+
|
442
850
|
# @!visibility private
|
443
851
|
def xcrun
|
444
852
|
RunLoop::Xcrun.new
|
@@ -511,8 +919,28 @@ $ xcrun security find-identity -v -p codesigning
|
|
511
919
|
end
|
512
920
|
|
513
921
|
# @!visibility private
|
514
|
-
def
|
515
|
-
|
922
|
+
def http_client(options={})
|
923
|
+
if !@http_client
|
924
|
+
@http_client = RunLoop::HTTP::RetriableClient.new(server, options)
|
925
|
+
else
|
926
|
+
# If the options are different, create a new client
|
927
|
+
if options[:retries] != @http_client.retries ||
|
928
|
+
options[:timeout] != @http_client.timeout ||
|
929
|
+
options[:interval] != @http_client.interval
|
930
|
+
reset_http_client!
|
931
|
+
@http_client = RunLoop::HTTP::RetriableClient.new(server, options)
|
932
|
+
else
|
933
|
+
end
|
934
|
+
end
|
935
|
+
@http_client
|
936
|
+
end
|
937
|
+
|
938
|
+
# @!visibility private
|
939
|
+
def reset_http_client!
|
940
|
+
if @http_client
|
941
|
+
@http_client.reset_all!
|
942
|
+
@http_client = nil
|
943
|
+
end
|
516
944
|
end
|
517
945
|
|
518
946
|
# @!visibility private
|
@@ -549,87 +977,108 @@ $ xcrun security find-identity -v -p codesigning
|
|
549
977
|
end
|
550
978
|
end
|
551
979
|
|
980
|
+
# @!visibility private
|
981
|
+
def server_pid
|
982
|
+
options = http_options
|
983
|
+
request = request("pid")
|
984
|
+
client = http_client(options)
|
985
|
+
response = client.get(request)
|
986
|
+
expect_300_response(response)
|
987
|
+
end
|
988
|
+
|
989
|
+
# @!visibility private
|
990
|
+
def session_identifier
|
991
|
+
options = http_options
|
992
|
+
request = request("sessionIdentifier")
|
993
|
+
client = http_client(options)
|
994
|
+
response = client.get(request)
|
995
|
+
expect_300_response(response)
|
996
|
+
end
|
997
|
+
|
552
998
|
# @!visibility private
|
553
999
|
def session_delete
|
554
1000
|
# https://xamarin.atlassian.net/browse/TCFW-255
|
555
1001
|
# httpclient is unable to send a valid DELETE
|
556
1002
|
args = ["curl", "-X", "DELETE", %Q[#{url}#{versioned_route("session")}]]
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
# response = client.delete(request)
|
564
|
-
# body = expect_200_response(response)
|
565
|
-
# RunLoop.log_debug("CBX-Runner says, #{body}")
|
566
|
-
# body
|
567
|
-
# rescue => e
|
568
|
-
# RunLoop.log_debug("CBX-Runner session delete error: #{e}")
|
569
|
-
# nil
|
570
|
-
# end
|
1003
|
+
|
1004
|
+
begin
|
1005
|
+
run_shell_command(args, {:log_cmd => true, :timeout => 10})
|
1006
|
+
rescue Shell::TimeoutError => _
|
1007
|
+
RunLoop.log_debug("Timed out calling DELETE session/ after 10 seconds")
|
1008
|
+
end
|
571
1009
|
end
|
572
1010
|
|
573
1011
|
# @!visibility private
|
574
|
-
# TODO expect 200 response and parse body (atm the body in not valid JSON)
|
575
1012
|
def shutdown
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
1013
|
+
|
1014
|
+
if RunLoop::Environment.xtc?
|
1015
|
+
RunLoop.log_error("Calling shutdown on the XTC is not supported.")
|
1016
|
+
return
|
1017
|
+
end
|
1018
|
+
|
581
1019
|
begin
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
1020
|
+
if !running?
|
1021
|
+
RunLoop.log_debug("DeviceAgent-Runner is not running")
|
1022
|
+
else
|
1023
|
+
session_delete
|
1024
|
+
|
1025
|
+
request = request("shutdown")
|
1026
|
+
client = http_client(ping_options)
|
1027
|
+
response = client.post(request)
|
1028
|
+
hash = expect_300_response(response)
|
1029
|
+
message = hash["message"]
|
1030
|
+
|
1031
|
+
RunLoop.log_debug(%Q[DeviceAgent-Runner says, "#{message}"])
|
1032
|
+
|
1033
|
+
now = Time.now
|
1034
|
+
poll_until = now + 10.0
|
1035
|
+
stopped = false
|
1036
|
+
while Time.now < poll_until
|
1037
|
+
stopped = !running?
|
1038
|
+
break if stopped
|
1039
|
+
sleep(0.1)
|
1040
|
+
end
|
594
1041
|
|
595
|
-
|
596
|
-
|
597
|
-
rescue => e
|
598
|
-
RunLoop.log_debug("DeviceAgent-Runner shutdown error: #{e}")
|
1042
|
+
RunLoop.log_debug("Waited for #{Time.now - now} seconds for DeviceAgent to shutdown")
|
1043
|
+
end
|
1044
|
+
rescue RunLoop::DeviceAgent::Client::HTTPError => e
|
1045
|
+
RunLoop.log_debug("DeviceAgent-Runner shutdown error: #{e.message}")
|
599
1046
|
ensure
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
1047
|
+
if @launcher_pid
|
1048
|
+
term_options = { :timeout => 1.5 }
|
1049
|
+
kill_options = { :timeout => 1.0 }
|
1050
|
+
|
1051
|
+
process_name = cbx_launcher.name
|
1052
|
+
pid = @launcher_pid.to_i
|
1053
|
+
|
1054
|
+
term = RunLoop::ProcessTerminator.new(pid, "TERM", process_name, term_options)
|
1055
|
+
if !term.kill_process
|
1056
|
+
kill = RunLoop::ProcessTerminator.new(pid, "KILL", process_name, kill_options)
|
1057
|
+
kill.kill_process
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
if process_name == :xcodebuild
|
1061
|
+
sleep(10)
|
613
1062
|
end
|
614
1063
|
end
|
615
1064
|
end
|
616
|
-
|
1065
|
+
hash
|
617
1066
|
end
|
618
1067
|
|
619
1068
|
# @!visibility private
|
620
|
-
# TODO expect 200 response and parse body (atm the body is not valid JSON)
|
621
1069
|
def health(options={})
|
622
1070
|
merged_options = http_options.merge(options)
|
623
1071
|
request = request("health")
|
624
|
-
client =
|
1072
|
+
client = http_client(merged_options)
|
625
1073
|
response = client.get(request)
|
626
|
-
|
627
|
-
|
628
|
-
|
1074
|
+
hash = expect_300_response(response)
|
1075
|
+
status = hash["status"]
|
1076
|
+
RunLoop.log_debug(%Q[DeviceAgent says, "#{status}"])
|
1077
|
+
hash
|
629
1078
|
end
|
630
1079
|
|
631
|
-
|
632
|
-
#
|
1080
|
+
# TODO Might not be necessary - this is an edge case and it is likely
|
1081
|
+
# that iOSDeviceManager will be able to handle this for us.
|
633
1082
|
def cbx_runner_stale?
|
634
1083
|
false
|
635
1084
|
# The RunLoop::Version class needs to be updated to handle timestamps.
|
@@ -646,58 +1095,35 @@ $ xcrun security find-identity -v -p codesigning
|
|
646
1095
|
end
|
647
1096
|
|
648
1097
|
# @!visibility private
|
649
|
-
def launch_cbx_runner
|
650
|
-
|
1098
|
+
def launch_cbx_runner
|
1099
|
+
options = launcher_options
|
651
1100
|
|
652
|
-
if
|
1101
|
+
if options[:shutdown_device_agent_before_launch]
|
653
1102
|
RunLoop.log_debug("Launch options insist that the DeviceAgent be shutdown")
|
654
1103
|
shutdown
|
1104
|
+
end
|
655
1105
|
|
656
|
-
|
657
|
-
|
658
|
-
|
1106
|
+
if cbx_runner_stale?
|
1107
|
+
RunLoop.log_debug("The DeviceAgent that is running is stale; shutting down")
|
1108
|
+
shutdown
|
659
1109
|
end
|
660
1110
|
|
661
1111
|
if running?
|
662
1112
|
RunLoop.log_debug("DeviceAgent is already running")
|
663
|
-
|
664
|
-
shutdown
|
665
|
-
else
|
666
|
-
# TODO: is it necessary to return the pid? Or can we return true?
|
667
|
-
return server_pid
|
668
|
-
end
|
669
|
-
end
|
670
|
-
|
671
|
-
if cbx_launcher.name == :xcodebuild
|
672
|
-
RunLoop.log_debug("xcodebuild is the launcher - terminating existing xcodebuild processes")
|
673
|
-
term_options = { :timeout => 0.5 }
|
674
|
-
kill_options = { :timeout => 0.5 }
|
675
|
-
RunLoop::ProcessWaiter.new("xcodebuild").pids.each do |pid|
|
676
|
-
term = RunLoop::ProcessTerminator.new(pid, 'TERM', "xcodebuild", term_options)
|
677
|
-
killed = term.kill_process
|
678
|
-
unless killed
|
679
|
-
RunLoop::ProcessTerminator.new(pid, 'KILL', "xcodebuild", kill_options)
|
680
|
-
end
|
681
|
-
end
|
682
|
-
sleep(2.0)
|
1113
|
+
return true
|
683
1114
|
end
|
684
1115
|
|
685
1116
|
start = Time.now
|
686
|
-
RunLoop.log_debug("Waiting for
|
687
|
-
|
688
|
-
|
689
|
-
if cbx_launcher.name == :xcodebuild
|
690
|
-
sleep(2.0)
|
691
|
-
end
|
1117
|
+
RunLoop.log_debug("Waiting for DeviceAgent to launch...")
|
1118
|
+
@launcher_pid = cbx_launcher.launch(options)
|
692
1119
|
|
693
1120
|
begin
|
694
|
-
timeout =
|
1121
|
+
timeout = options[:device_agent_install_timeout] * 1.5
|
695
1122
|
health_options = {
|
696
1123
|
:timeout => timeout,
|
697
1124
|
:interval => 0.1,
|
698
1125
|
:retries => (timeout/0.1).to_i
|
699
1126
|
}
|
700
|
-
|
701
1127
|
health(health_options)
|
702
1128
|
rescue RunLoop::HTTP::Error => _
|
703
1129
|
raise %Q[
|
@@ -715,16 +1141,15 @@ $ tail -1000 -F #{cbx_launcher.class.log_file}
|
|
715
1141
|
end
|
716
1142
|
|
717
1143
|
RunLoop.log_debug("Took #{Time.now - start} launch and respond to /health")
|
718
|
-
|
719
|
-
# TODO: is it necessary to return the pid? Or can we return true?
|
720
|
-
pid
|
1144
|
+
true
|
721
1145
|
end
|
722
1146
|
|
723
1147
|
# @!visibility private
|
724
1148
|
def launch_aut(bundle_id = @bundle_id)
|
725
|
-
client =
|
1149
|
+
client = http_client(http_options)
|
726
1150
|
request = request("session", {:bundleID => bundle_id})
|
727
1151
|
|
1152
|
+
# This check needs to be done _before_ the DeviceAgent is launched.
|
728
1153
|
if device.simulator?
|
729
1154
|
# Yes, we could use iOSDeviceManager to check, I dont understand the
|
730
1155
|
# behavior yet - does it require the simulator be launched?
|
@@ -736,7 +1161,10 @@ $ tail -1000 -F #{cbx_launcher.class.log_file}
|
|
736
1161
|
RunLoop.log_debug("Detected :xcodebuild launcher; skipping app installed check")
|
737
1162
|
installed = true
|
738
1163
|
else
|
739
|
-
|
1164
|
+
# Too slow for most devices
|
1165
|
+
# https://jira.xamarin.com/browse/TCFW-273
|
1166
|
+
# installed = cbx_launcher.app_installed?(bundle_id)
|
1167
|
+
installed = true
|
740
1168
|
end
|
741
1169
|
end
|
742
1170
|
|
@@ -752,17 +1180,28 @@ Please install it.
|
|
752
1180
|
]
|
753
1181
|
end
|
754
1182
|
|
1183
|
+
retries = 5
|
1184
|
+
|
755
1185
|
begin
|
756
1186
|
response = client.post(request)
|
757
1187
|
RunLoop.log_debug("Launched #{bundle_id} on #{device}")
|
758
1188
|
RunLoop.log_debug("#{response.body}")
|
759
|
-
|
760
|
-
|
761
|
-
# in the simulator_wait_for_stable_state; it waits too long.
|
762
|
-
# device.simulator_wait_for_stable_state
|
763
|
-
end
|
764
|
-
expect_200_response(response)
|
1189
|
+
|
1190
|
+
expect_300_response(response)
|
765
1191
|
rescue => e
|
1192
|
+
retries = retries - 1
|
1193
|
+
if !RunLoop::Environment.xtc?
|
1194
|
+
if retries >= 0
|
1195
|
+
if !running?
|
1196
|
+
RunLoop.log_debug("The DeviceAgent stopped running after POST /session; retrying")
|
1197
|
+
launch_cbx_runner
|
1198
|
+
else
|
1199
|
+
RunLoop.log_debug("Failed to launch the AUT: #{bundle_id}; retrying")
|
1200
|
+
end
|
1201
|
+
retry
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
|
766
1205
|
raise e.class, %Q[
|
767
1206
|
|
768
1207
|
Could not launch #{bundle_id} on #{device}:
|
@@ -781,35 +1220,44 @@ Something went wrong.
|
|
781
1220
|
begin
|
782
1221
|
JSON.parse(body)
|
783
1222
|
rescue TypeError, JSON::ParserError => _
|
784
|
-
raise RunLoop::DeviceAgent::Client::HTTPError,
|
785
|
-
|
1223
|
+
raise RunLoop::DeviceAgent::Client::HTTPError, %Q[
|
1224
|
+
Could not parse response from server:
|
1225
|
+
|
1226
|
+
body => "#{body}"
|
1227
|
+
|
1228
|
+
If the body empty, the DeviceAgent has probably crashed.
|
1229
|
+
|
1230
|
+
]
|
786
1231
|
end
|
787
1232
|
end
|
788
1233
|
|
789
1234
|
# @!visibility private
|
790
|
-
def
|
1235
|
+
def expect_300_response(response)
|
791
1236
|
body = response_body_to_hash(response)
|
792
|
-
if response.status_code <
|
1237
|
+
if response.status_code < 400 && !body["error"]
|
793
1238
|
return body
|
794
1239
|
end
|
795
1240
|
|
796
|
-
|
1241
|
+
reset_http_client!
|
1242
|
+
|
1243
|
+
if response.status_code >= 400
|
797
1244
|
raise RunLoop::DeviceAgent::Client::HTTPError,
|
798
|
-
%Q[
|
1245
|
+
%Q[
|
1246
|
+
Expected status code < 400, found #{response.status_code}.
|
799
1247
|
|
800
1248
|
Server replied with:
|
801
1249
|
|
802
1250
|
#{body}
|
803
1251
|
|
804
|
-
|
1252
|
+
]
|
805
1253
|
else
|
806
1254
|
raise RunLoop::DeviceAgent::Client::HTTPError,
|
807
|
-
%Q[
|
1255
|
+
%Q[
|
1256
|
+
Expected JSON response with no error, but found
|
808
1257
|
|
809
1258
|
#{body["error"]}
|
810
1259
|
|
811
|
-
|
812
|
-
|
1260
|
+
]
|
813
1261
|
end
|
814
1262
|
end
|
815
1263
|
|