run_loop 2.1.9 → 2.1.10
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/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
|
|