calabash-cucumber 0.19.2 → 0.20.0
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/dylibs/libCalabashDyn.dylib +0 -0
- data/dylibs/libCalabashDynSim.dylib +0 -0
- data/lib/calabash-cucumber.rb +9 -2
- data/lib/calabash-cucumber/abstract.rb +23 -0
- data/lib/calabash-cucumber/automator/automator.rb +158 -0
- data/lib/calabash-cucumber/automator/coordinates.rb +401 -0
- data/lib/calabash-cucumber/automator/device_agent.rb +424 -0
- data/lib/calabash-cucumber/automator/instruments.rb +441 -0
- data/lib/calabash-cucumber/connection_helpers.rb +1 -0
- data/lib/calabash-cucumber/core.rb +632 -138
- data/lib/calabash-cucumber/device_agent.rb +346 -0
- data/lib/calabash-cucumber/dot_dir.rb +1 -0
- data/lib/calabash-cucumber/environment.rb +1 -0
- data/lib/calabash-cucumber/environment_helpers.rb +4 -3
- data/lib/calabash-cucumber/http/http.rb +6 -4
- data/lib/calabash-cucumber/keyboard_helpers.rb +97 -679
- data/lib/calabash-cucumber/launcher.rb +107 -31
- data/lib/calabash-cucumber/log_tailer.rb +46 -0
- data/lib/calabash-cucumber/map.rb +7 -1
- data/lib/calabash-cucumber/rotation_helpers.rb +47 -139
- data/lib/calabash-cucumber/status_bar_helpers.rb +51 -20
- data/lib/calabash-cucumber/store/preferences.rb +3 -0
- data/lib/calabash-cucumber/uia.rb +333 -2
- data/lib/calabash-cucumber/usage_tracker.rb +2 -0
- data/lib/calabash-cucumber/version.rb +2 -2
- data/lib/calabash-cucumber/wait_helpers.rb +2 -0
- data/lib/calabash/formatters/html.rb +6 -1
- data/lib/frank-calabash.rb +10 -4
- data/scripts/.irbrc +3 -0
- data/staticlib/calabash.framework.zip +0 -0
- data/staticlib/libFrankCalabash.a +0 -0
- metadata +11 -6
- data/lib/calabash-cucumber/actions/instruments_actions.rb +0 -155
@@ -26,6 +26,9 @@ module Calabash
|
|
26
26
|
include Calabash::Cucumber::StatusBarHelpers
|
27
27
|
include Calabash::Cucumber::RotationHelpers
|
28
28
|
|
29
|
+
require "calabash-cucumber/keyboard_helpers"
|
30
|
+
include Calabash::Cucumber::KeyboardHelpers
|
31
|
+
|
29
32
|
# @!visibility private
|
30
33
|
# @deprecated Use Cucumber's step method.
|
31
34
|
#
|
@@ -158,9 +161,7 @@ module Calabash
|
|
158
161
|
#
|
159
162
|
# @param [String] uiquery a query specifying which objects to flash
|
160
163
|
# @param [Array] args argument is ignored and should be deprecated
|
161
|
-
# @return [Array] an array of that contains the
|
162
|
-
# objc selector `description` on each matching view.
|
163
|
-
#
|
164
|
+
# @return [Array] an array of that contains all the view matched.
|
164
165
|
def flash(uiquery, *args)
|
165
166
|
# todo deprecate the *args argument in the flash method
|
166
167
|
# todo :flash operation should return views as JSON objects
|
@@ -180,23 +181,111 @@ module Calabash
|
|
180
181
|
Calabash::Cucumber::VERSION
|
181
182
|
end
|
182
183
|
|
184
|
+
# Rotates the home button to a position relative to the status bar.
|
185
|
+
#
|
186
|
+
# @example portrait
|
187
|
+
# rotate_home_button_to :down
|
188
|
+
#
|
189
|
+
# @example upside down
|
190
|
+
# rotate_home_button_to :up
|
191
|
+
#
|
192
|
+
# @example landscape with left home button AKA: _right_ landscape
|
193
|
+
# rotate_home_button_to :left
|
194
|
+
#
|
195
|
+
# @example landscape with right home button AKA: _left_ landscape
|
196
|
+
# rotate_home_button_to :right
|
197
|
+
#
|
198
|
+
# Refer to Apple's documentation for clarification about left vs.
|
199
|
+
# right landscape orientations.
|
200
|
+
#
|
201
|
+
# For legacy support the `dir` argument can be a String or Symbol.
|
202
|
+
# Please update your code to pass a Symbol.
|
203
|
+
#
|
204
|
+
# For legacy support `:top` and `top` are synonyms for `:up`.
|
205
|
+
# Please update your code to pass `:up`.
|
206
|
+
#
|
207
|
+
# For legacy support `:bottom` and `bottom` are synonyms for `:down`.
|
208
|
+
# Please update your code to pass `:down`.
|
209
|
+
#
|
210
|
+
# @param [Symbol] position The position of the home button after the rotation.
|
211
|
+
# Can be one of `{:down | :left | :right | :up }`.
|
212
|
+
#
|
213
|
+
# @note A rotation will only occur if your view controller and application
|
214
|
+
# support the target orientation.
|
215
|
+
#
|
216
|
+
# @return [Symbol] The position of the home button relative to the status
|
217
|
+
# bar when all rotations have been completed.
|
218
|
+
def rotate_home_button_to(position)
|
219
|
+
|
220
|
+
normalized_symbol = expect_valid_rotate_home_to_arg(position)
|
221
|
+
current_orientation = status_bar_orientation.to_sym
|
222
|
+
|
223
|
+
return current_orientation if current_orientation == normalized_symbol
|
224
|
+
|
225
|
+
launcher.automator.send(:rotate_home_button_to, normalized_symbol)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Rotates the device in the direction indicated by `direction`.
|
229
|
+
#
|
230
|
+
# @example rotate left
|
231
|
+
# rotate :left
|
232
|
+
#
|
233
|
+
# @example rotate right
|
234
|
+
# rotate :right
|
235
|
+
#
|
236
|
+
# @param [Symbol] direction The direction to rotate. Can be :left or :right.
|
237
|
+
#
|
238
|
+
# @return [Symbol] The position of the home button relative to the status
|
239
|
+
# bar after the rotation. Will be one of `{:down | :left | :right | :up }`.
|
240
|
+
# @raise [ArgumentError] If direction is not :left or :right.
|
241
|
+
def rotate(direction)
|
242
|
+
as_symbol = direction.to_sym
|
243
|
+
|
244
|
+
if as_symbol != :left && as_symbol != :right
|
245
|
+
raise ArgumentError,
|
246
|
+
"Expected '#{direction}' to be :left or :right"
|
247
|
+
end
|
248
|
+
|
249
|
+
launcher.automator.send(:rotate, as_symbol)
|
250
|
+
end
|
251
|
+
|
183
252
|
# Performs the `tap` gesture on the (first) view that matches
|
184
|
-
# query `uiquery`. Note that `touch` assumes the view is visible and not
|
185
|
-
# If the view is not visible `touch` will fail. If the view is
|
186
|
-
# `touch` will *silently* fail.
|
253
|
+
# query `uiquery`. Note that `touch` assumes the view is visible and not
|
254
|
+
# animating. If the view is not visible `touch` will fail. If the view is
|
255
|
+
# animating `touch` will *silently* fail.
|
256
|
+
#
|
187
257
|
# By default, taps the center of the view.
|
188
258
|
# @see Calabash::Cucumber::WaitHelpers#wait_tap
|
189
259
|
# @see Calabash::Cucumber::Operations#tap_mark
|
190
260
|
# @see #tap_point
|
191
|
-
#
|
192
|
-
#
|
261
|
+
#
|
262
|
+
# @param {String} uiquery query describing view to tap. If this value is
|
263
|
+
# `nil` then an :offset must be passed as an option. This can be used
|
264
|
+
# to tap a specific coordinate.
|
193
265
|
# @param {Hash} options option for modifying the details of the touch
|
194
|
-
# @option options {Hash} :offset (nil) optional offset to touch point.
|
195
|
-
#
|
196
|
-
#
|
266
|
+
# @option options {Hash} :offset (nil) optional offset to touch point.
|
267
|
+
# Offset supports an `:x` and `:y` key and causes the touch to be offset
|
268
|
+
# with `(x,y)` relative to the center.
|
269
|
+
#
|
270
|
+
# @return {Array<Hash>} array containing the serialized version of the
|
271
|
+
# tapped view.
|
272
|
+
#
|
273
|
+
# @raise [RuntimeError] If query is non nil and matches no views.
|
274
|
+
# @raise [ArgumentError] If query is nil and there is no :offset in the
|
275
|
+
# the options. The offset must contain both an :x and :y value.
|
197
276
|
def touch(uiquery, options={})
|
198
|
-
if uiquery.nil?
|
199
|
-
|
277
|
+
if uiquery.nil?
|
278
|
+
offset = options[:offset]
|
279
|
+
|
280
|
+
if !(offset && offset[:x] && offset[:y])
|
281
|
+
raise ArgumentError, %Q[
|
282
|
+
If query is nil, there must be a valid offset in the options.
|
283
|
+
|
284
|
+
Expected: options[:offset] = {:x => NUMERIC, :y => NUMERIC}
|
285
|
+
Actual: options[:offset] = #{offset ? offset : "nil"}
|
286
|
+
|
287
|
+
]
|
288
|
+
end
|
200
289
|
end
|
201
290
|
query_action_with_options(:touch, uiquery, options)
|
202
291
|
end
|
@@ -236,8 +325,8 @@ module Calabash
|
|
236
325
|
#
|
237
326
|
# @note This assumes the view is visible and not animating.
|
238
327
|
#
|
239
|
-
# If the view is not visible it will fail with an error. If the view is
|
240
|
-
# it will *silently* fail.
|
328
|
+
# If the view is not visible it will fail with an error. If the view is
|
329
|
+
# animating it will *silently* fail.
|
241
330
|
#
|
242
331
|
# By default, taps the center of the view.
|
243
332
|
#
|
@@ -245,60 +334,22 @@ module Calabash
|
|
245
334
|
# two_finger_tap "view marked:'Third'", offset:{x:100}
|
246
335
|
# @param {String} uiquery query describing view to touch.
|
247
336
|
# @param {Hash} options option for modifying the details of the touch.
|
248
|
-
# @option options {Hash} :offset (nil) optional offset to touch point.
|
249
|
-
#
|
250
|
-
#
|
337
|
+
# @option options {Hash} :offset (nil) optional offset to touch point.
|
338
|
+
# Offset supports an `:x` and `:y` key and causes the touch to be offset
|
339
|
+
# with `(x,y)` relative to the center (`center + (offset[:x], offset[:y])`).
|
340
|
+
# @return {Array<Hash>} array containing the serialized version of the
|
341
|
+
# tapped view.
|
251
342
|
def two_finger_tap(uiquery,options={})
|
252
343
|
query_action_with_options(:two_finger_tap, uiquery, options)
|
253
344
|
end
|
254
345
|
|
255
|
-
# Performs the "
|
256
|
-
# query `uiquery`.
|
346
|
+
# Performs the "long press" or "touch and hold" gesture on the (first)
|
347
|
+
# view that matches query `uiquery`.
|
257
348
|
#
|
258
349
|
# @note This assumes the view is visible and not animating.
|
259
350
|
#
|
260
|
-
# If the view is not visible it will fail with an error. If the view is
|
261
|
-
# it will *silently* fail.
|
262
|
-
#
|
263
|
-
# By default, the gesture starts at the center of the view and "flicks" according to `delta`.
|
264
|
-
#
|
265
|
-
# A flick is similar to a swipe.
|
266
|
-
#
|
267
|
-
# @example
|
268
|
-
# flick("MKMapView", {x:100,y:50})
|
269
|
-
# @note Due to a bug in the iOS Simulator (or UIAutomation on the simulator)
|
270
|
-
# swiping and other 'advanced' gestures are not supported in certain
|
271
|
-
# scroll views (e.g. UITableView or UIScrollView). It does work when running
|
272
|
-
# on physical devices, though, Here is a link to a relevant Stack Overflow post
|
273
|
-
# http://stackoverflow.com/questions/18792965/uiautomations-draginsidewithoptions-has-no-effect-on-ios7-simulator
|
274
|
-
# It is not a bug in Calabash itself but rather in UIAutomation and hence we can't just
|
275
|
-
# fix it. The work around is typically to use the scroll_to_* functions.
|
276
|
-
#
|
277
|
-
# @param {String} uiquery query describing view to touch.
|
278
|
-
# @param {Hash} delta coordinate describing the direction to flick
|
279
|
-
# @param {Hash} options option for modifying the details of the touch.
|
280
|
-
# @option options {Hash} :offset (nil) optional offset to touch point. Offset supports an `:x` and `:y` key
|
281
|
-
# and causes the touch to be offset with `(x,y)` relative to the center (`center + (offset[:x], offset[:y])`).
|
282
|
-
# @option delta {Numeric} :x (0) optional. The force and direction of the flick on the `x`-axis
|
283
|
-
# @option delta {Numeric} :y (0) optional. The force and direction of the flick on the `y`-axis
|
284
|
-
# @return {Array<Hash>} array containing the serialized version of the touched view.
|
285
|
-
def flick(uiquery, delta, options={})
|
286
|
-
uiquery, options = extract_query_and_options(uiquery, options)
|
287
|
-
options[:delta] = delta
|
288
|
-
views_touched = launcher.actions.flick(options)
|
289
|
-
unless uiquery.nil?
|
290
|
-
screenshot_and_raise "flick could not find view: '#{uiquery}', args: #{options}" if views_touched.empty?
|
291
|
-
end
|
292
|
-
views_touched
|
293
|
-
end
|
294
|
-
|
295
|
-
# Performs the "long press" or "touch and hold" gesture on the (first) view that matches
|
296
|
-
# query `uiquery`.
|
297
|
-
#
|
298
|
-
# @note This assumes the view is visible and not animating.
|
299
|
-
#
|
300
|
-
# If the view is not visible it will fail with an error. If the view is animating
|
301
|
-
# it will *silently* fail.
|
351
|
+
# If the view is not visible it will fail with an error. If the view is
|
352
|
+
# animating it will *silently* fail.
|
302
353
|
#
|
303
354
|
# By default, the gesture starts at the center of the view.
|
304
355
|
#
|
@@ -306,71 +357,230 @@ module Calabash
|
|
306
357
|
# touch_hold "webView css:'input'", duration:10, offset:{x: -40}
|
307
358
|
# @param {String} uiquery query describing view to touch.
|
308
359
|
# @param {Hash} options option for modifying the details of the touch.
|
309
|
-
# @option options {Hash} :offset (nil) optional offset to touch point.
|
310
|
-
#
|
360
|
+
# @option options {Hash} :offset (nil) optional offset to touch point.
|
361
|
+
# Offset supports an `:x` and `:y` key and causes the touch to be offset
|
362
|
+
# with `(x,y)` relative to the center (`center + (offset[:x], offset[:y])`).
|
311
363
|
# @option options {Numeric} :duration (3) duration of the 'hold'.
|
312
|
-
# @return {Array<Hash>} array containing the serialized version of the
|
364
|
+
# @return {Array<Hash>} array containing the serialized version of the
|
365
|
+
# touched view.
|
313
366
|
def touch_hold(uiquery, options={})
|
314
367
|
query_action_with_options(:touch_hold, uiquery, options)
|
315
368
|
end
|
316
369
|
|
317
370
|
# Performs a "swipe" gesture.
|
318
|
-
# By default, the gesture starts at the center of the screen.
|
319
371
|
#
|
320
|
-
# @
|
321
|
-
# first argument. We should migrate this.
|
372
|
+
# @example
|
322
373
|
#
|
323
|
-
#
|
324
|
-
#
|
325
|
-
# {https://github.com/calabash/calabash-ios/issues/253}
|
374
|
+
# # Swipe left on first view match by "*"
|
375
|
+
# swipe(:left)
|
326
376
|
#
|
327
|
-
#
|
328
|
-
# swipe :
|
329
|
-
# @example
|
330
|
-
# swipe :down, offset:{x:10,y:50}, query:"MKMapView"
|
331
|
-
# @param {String} dir the direction to swipe (symbols can also be used).
|
332
|
-
# @param {Hash} options option for modifying the details of the touch.
|
333
|
-
# @option options {Hash} :offset (nil) optional offset to touch point. Offset supports an `:x` and `:y` key
|
334
|
-
# and causes the touch to be offset with `(x,y)` relative to the center (`center + (offset[:x], offset[:y])`).
|
335
|
-
# @option options {String} :query (nil) if specified, the swipe will be made relative to this query.
|
336
|
-
# @option options [Symbol] :force (nil) Indicates the force of the swipe.
|
337
|
-
# Valid values are :strong, :normal, :light.
|
377
|
+
# # Swipe up on 'my scroll view'
|
378
|
+
# swipe(:up, {:query => "* marked:'my scroll view'"})
|
338
379
|
#
|
339
|
-
# @
|
340
|
-
|
341
|
-
|
380
|
+
# @param {String, Symbol} direction The direction to swipe
|
381
|
+
# @param {Hash} options Options for modifying the details of the swipe.
|
382
|
+
# @option options {Hash} :offset (nil) optional offset to touch point.
|
383
|
+
# Offset supports an `:x` and `:y` key and causes the touch to be
|
384
|
+
# offset with `(x,y)` relative to the center.
|
385
|
+
# @option options {String} :query (nil) If specified, the swipe will be
|
386
|
+
# made on the first view matching this query. If this option is nil
|
387
|
+
# (the default), the swipe will happen on the first view matched by "*".
|
388
|
+
# @option options [Symbol] :force (normal) Indicates the force of the
|
389
|
+
# swipe. Valid values are :strong, :normal, :light.
|
390
|
+
#
|
391
|
+
# @return {Array<Hash>,String} An array with one element; the view that
|
392
|
+
# was swiped.
|
393
|
+
#
|
394
|
+
# @raise [ArgumentError] If :force is invalid.
|
395
|
+
# @raise [ArgumentError] If direction is invalid
|
396
|
+
def swipe(direction, options={})
|
397
|
+
merged_options = {
|
398
|
+
:query => nil,
|
399
|
+
:force => :normal
|
400
|
+
}.merge(options)
|
342
401
|
|
343
|
-
|
344
|
-
unless uia_available?
|
345
|
-
merged_options[:status_bar_orientation] = status_bar_orientation
|
346
|
-
end
|
402
|
+
merged_options[:direction] = direction.to_sym
|
347
403
|
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
404
|
+
if ![:up, :down, :left, :right].include?(merged_options[:direction])
|
405
|
+
raise ArgumentError, %Q[
|
406
|
+
Invalid direction argument: '#{direction}'.
|
407
|
+
|
408
|
+
Valid directions are: :up, :down, :left, and :right
|
409
|
+
|
410
|
+
]
|
354
411
|
end
|
355
412
|
|
356
|
-
|
413
|
+
if ![:light, :strong, :normal].include?(merged_options[:force])
|
414
|
+
raise ArgumentError, %Q[
|
415
|
+
Invalid force option: '#{merged_options[:force]}'.
|
416
|
+
|
417
|
+
Valid forces are: :strong, :normal, :light
|
418
|
+
|
419
|
+
]
|
420
|
+
end
|
421
|
+
|
422
|
+
launcher.automator.swipe(merged_options)
|
357
423
|
end
|
358
424
|
|
425
|
+
# Performs the "flick" gesture on the first view that matches `uiquery`.
|
426
|
+
#
|
427
|
+
# If the view is not visible it will fail with an error.
|
428
|
+
#
|
429
|
+
# If the view is animating it will *silently* fail.
|
430
|
+
#
|
431
|
+
# By default, the gesture starts at the center of the view and "flicks"
|
432
|
+
# according to `delta`.
|
433
|
+
#
|
434
|
+
# A flick is a swipe with velocity.
|
435
|
+
#
|
436
|
+
# @example
|
437
|
+
# # Flick left: move screen to the right
|
438
|
+
# delta = {:x => -124.0, :y => 0.0}
|
439
|
+
#
|
440
|
+
# # Flick right: move screen to the left
|
441
|
+
# delta = {:x => 124.0, :y => 0.0}
|
442
|
+
#
|
443
|
+
# # Flick up: move screen to the bottom
|
444
|
+
# delta = {:x => 0, :y => -124.0}
|
445
|
+
#
|
446
|
+
# # Flick down: move screen to the top
|
447
|
+
# delta = {:x => 0, :y => 124.0}
|
448
|
+
#
|
449
|
+
# # Flick up and to the left: move the screen to the lower right corner
|
450
|
+
# delta = {:x => -88, :y => -88}
|
451
|
+
#
|
452
|
+
# flick("MKMapView", delta)
|
453
|
+
#
|
454
|
+
# @param {String} uiquery query describing view to flick.
|
455
|
+
# @param {Hash} delta coordinate describing the direction to flick
|
456
|
+
# @param {Hash} options option for modifying the details of the flick.
|
457
|
+
# @option options {Hash} :offset (nil) optional offset to touch point.
|
458
|
+
# Offset supports an `:x` and `:y` key and causes the first touch to be
|
459
|
+
# offset with `(x,y)` relative to the center.
|
460
|
+
# @return {Array<Hash>} array containing the serialized version of the touched view.
|
461
|
+
#
|
462
|
+
# @raise [ArgumentError] If query is nil.
|
463
|
+
def flick(uiquery, delta, options={})
|
464
|
+
if uiquery.nil?
|
465
|
+
raise ArgumentError, "Query argument cannot be nil"
|
466
|
+
end
|
467
|
+
|
468
|
+
merged_options = {
|
469
|
+
:delta => delta
|
470
|
+
}.merge(options)
|
471
|
+
|
472
|
+
query_action_with_options(:flick, uiquery, merged_options)
|
473
|
+
end
|
359
474
|
|
360
|
-
# Performs the
|
361
|
-
#
|
475
|
+
# Performs the pan gesture between two coordinates.
|
476
|
+
#
|
477
|
+
# Swipes, scrolls, drag-and-drop, and flicks are all pan gestures.
|
478
|
+
#
|
362
479
|
# @example
|
363
|
-
#
|
364
|
-
#
|
480
|
+
# # Reorder table view rows.
|
481
|
+
# q1="* marked:'Reorder Apple'"
|
482
|
+
# q2="* marked:'Reorder Google'"
|
365
483
|
# pan q1, q2, duration:4
|
366
|
-
#
|
367
|
-
# @param {String}
|
368
|
-
# @
|
369
|
-
#
|
370
|
-
#
|
371
|
-
#
|
372
|
-
|
373
|
-
|
484
|
+
#
|
485
|
+
# @param {String} from_query query describing view to start the gesture
|
486
|
+
# @param {String} to_query query describing view to end the gesture
|
487
|
+
# @option options {Hash} :offset (nil) optional offset to touch point.
|
488
|
+
# Offset supports an `:x` and `:y` key and causes the pan to be offset
|
489
|
+
# with `(x,y)` relative to the center.
|
490
|
+
# @option options {Numeric} :duration (1.0) duration of the 'pan'. The
|
491
|
+
# minimum value of pan in UIAutomation is 0.5. For DeviceAgent, the
|
492
|
+
# duration must be > 0.
|
493
|
+
# @return {Array<Hash>} array containing the serialized version of the
|
494
|
+
# touched views. The first element is the first view matched by
|
495
|
+
# the from_query and the second element is the first view matched by
|
496
|
+
# the to_query.
|
497
|
+
#
|
498
|
+
# @raise [ArgumentError] If duration is < 0.5 for UIAutomation and <= 0
|
499
|
+
# for DeviceAgent.
|
500
|
+
def pan(from_query, to_query, options={})
|
501
|
+
merged_options = {
|
502
|
+
# Minimum value for UIAutomation is 0.5.
|
503
|
+
# DeviceAgent duration must be > 0.
|
504
|
+
:duration => 1.0
|
505
|
+
}.merge(options)
|
506
|
+
|
507
|
+
duration = merged_options[:duration]
|
508
|
+
|
509
|
+
if uia_available? && duration < 0.5
|
510
|
+
raise ArgumentError, %Q[
|
511
|
+
Invalid duration: #{duration}
|
512
|
+
|
513
|
+
The minimum duration is 0.5
|
514
|
+
|
515
|
+
]
|
516
|
+
elsif duration <= 0.0
|
517
|
+
raise ArgumentError, %Q[
|
518
|
+
Invalid duration: #{duration}
|
519
|
+
|
520
|
+
The minimum duration is 0.0.
|
521
|
+
|
522
|
+
]
|
523
|
+
end
|
524
|
+
|
525
|
+
launcher.automator.pan(from_query, to_query, merged_options)
|
526
|
+
end
|
527
|
+
|
528
|
+
# Performs the pan gesture between two coordinates.
|
529
|
+
#
|
530
|
+
# Swipes, scrolls, drag-and-drop, and flicks are all pan gestures.
|
531
|
+
#
|
532
|
+
# @example
|
533
|
+
# # Pan to go back in UINavigationController
|
534
|
+
# element = query("*").first
|
535
|
+
# y = element["rect"]["center_y"]
|
536
|
+
# pan_coordinates({10, y}, {160, y})
|
537
|
+
#
|
538
|
+
# # Pan to reveal Today and Notifications
|
539
|
+
# element = query("*").first
|
540
|
+
# x = element["rect"]["center_x"]
|
541
|
+
# pan_coordinates({x, 0}, {x, 240})
|
542
|
+
#
|
543
|
+
# # Pan to reveal Control Panel
|
544
|
+
# element = query("*").first
|
545
|
+
# x = element["rect"]["center_x"]
|
546
|
+
# y = element["rect"]["height"]
|
547
|
+
# pan_coordinates({x, height}, {x, 240})
|
548
|
+
#
|
549
|
+
# @param {Hash} from_point where to start the pan.
|
550
|
+
# @param {Hash} to_point where to end the pan.
|
551
|
+
# @option options {Numeric} :duration (1.0) duration of the 'pan'. The
|
552
|
+
# minimum value of pan in UIAutomation is 0.5. For DeviceAgent, the
|
553
|
+
# duration must be > 0.
|
554
|
+
#
|
555
|
+
# @raise [ArgumentError] If duration is < 0.5 for UIAutomation and <= 0
|
556
|
+
# for DeviceAgent.
|
557
|
+
def pan_coordinates(from_point, to_point, options={})
|
558
|
+
merged_options = {
|
559
|
+
# Minimum value for UIAutomation is 0.5.
|
560
|
+
# DeviceAgent duration must be > 0.
|
561
|
+
:duration => 1.0
|
562
|
+
}.merge(options)
|
563
|
+
|
564
|
+
duration = merged_options[:duration]
|
565
|
+
|
566
|
+
if uia_available? && duration < 0.5
|
567
|
+
raise ArgumentError, %Q[
|
568
|
+
Invalid duration: #{duration}
|
569
|
+
|
570
|
+
The minimum duration is 0.5
|
571
|
+
|
572
|
+
]
|
573
|
+
elsif duration <= 0.0
|
574
|
+
raise ArgumentError, %Q[
|
575
|
+
Invalid duration: #{duration}
|
576
|
+
|
577
|
+
The minimum duration is 0.0.
|
578
|
+
|
579
|
+
]
|
580
|
+
end
|
581
|
+
|
582
|
+
launcher.automator.pan_coordinates(from_point, to_point,
|
583
|
+
merged_options)
|
374
584
|
end
|
375
585
|
|
376
586
|
# Performs a "pinch" gesture.
|
@@ -387,16 +597,188 @@ module Calabash
|
|
387
597
|
# @option options {String} :query (nil) if specified, the pinch will be made relative to this query.
|
388
598
|
# @return {Array<Hash>,String} array containing the serialized version of the touched view if `options[:query]` is given.
|
389
599
|
def pinch(in_out, options={})
|
390
|
-
launcher.
|
600
|
+
launcher.automator.pinch(in_out.to_sym, options)
|
601
|
+
end
|
602
|
+
|
603
|
+
# @deprecated 0.21.0 Use #keyboard_enter_text
|
604
|
+
#
|
605
|
+
# Use keyboard to enter a character.
|
606
|
+
#
|
607
|
+
# @note
|
608
|
+
# There are several special 'characters', some of which do not appear on
|
609
|
+
# all keyboards; e.g. `Delete`, `Return`.
|
610
|
+
#
|
611
|
+
# @see #keyboard_enter_text
|
612
|
+
#
|
613
|
+
# @raise [RuntimeError] If there is no visible keyboard
|
614
|
+
# @raise [RuntimeError] If the keyboard (layout) is not supported
|
615
|
+
#
|
616
|
+
# @param [String] char The character to type
|
617
|
+
# @param [Hash] options Controls the behavior of the method.
|
618
|
+
# @option opts [Numeric] :wait_after_char (0.05) How long to sleep after
|
619
|
+
# typing a character.
|
620
|
+
def keyboard_enter_char(char, options={})
|
621
|
+
expect_keyboard_visible!
|
622
|
+
|
623
|
+
default_opts = {:wait_after_char => 0.05}
|
624
|
+
merged_options = default_opts.merge(options)
|
625
|
+
|
626
|
+
special_char = launcher.automator.char_for_keyboard_action(char)
|
627
|
+
|
628
|
+
if special_char
|
629
|
+
launcher.automator.enter_char_with_keyboard(special_char)
|
630
|
+
elsif char.length == 1
|
631
|
+
launcher.automator.enter_char_with_keyboard(char)
|
632
|
+
else
|
633
|
+
raise ArgumentError, %Q[
|
634
|
+
Expected '#{char}' to be a single character or a special string like:
|
635
|
+
|
636
|
+
* Return
|
637
|
+
* Delete
|
638
|
+
|
639
|
+
To type strings with more than one character, use keyboard_enter_text.
|
640
|
+
]
|
641
|
+
end
|
642
|
+
|
643
|
+
duration = merged_options[:wait_after_char]
|
644
|
+
if duration > 0
|
645
|
+
Kernel.sleep(duration)
|
646
|
+
end
|
647
|
+
|
648
|
+
[]
|
649
|
+
end
|
650
|
+
|
651
|
+
# Touches the keyboard action key.
|
652
|
+
#
|
653
|
+
# The action key depends on the keyboard. Some examples include:
|
654
|
+
#
|
655
|
+
# * Return
|
656
|
+
# * Next
|
657
|
+
# * Go
|
658
|
+
# * Join
|
659
|
+
# * Search
|
660
|
+
#
|
661
|
+
# @note
|
662
|
+
# Not all keyboards have an action key. For example, numeric keyboards
|
663
|
+
# do not have an action key.
|
664
|
+
#
|
665
|
+
# @raise [RuntimeError] If the keyboard is not visible.
|
666
|
+
def tap_keyboard_action_key
|
667
|
+
expect_keyboard_visible!
|
668
|
+
launcher.automator.tap_keyboard_action_key
|
669
|
+
end
|
670
|
+
|
671
|
+
# Touches the keyboard delete key.
|
672
|
+
#
|
673
|
+
# @raise [RuntimeError] If the keyboard is not visible.
|
674
|
+
def tap_keyboard_delete_key
|
675
|
+
expect_keyboard_visible!
|
676
|
+
launcher.automator.tap_keyboard_delete_key
|
677
|
+
end
|
678
|
+
|
679
|
+
# Uses the keyboard to enter text.
|
680
|
+
#
|
681
|
+
# @param [String] text the text to type.
|
682
|
+
# @raise [RuntimeError] If the keyboard is not visible.
|
683
|
+
def keyboard_enter_text(text)
|
684
|
+
expect_keyboard_visible!
|
685
|
+
existing_text = text_from_first_responder
|
686
|
+
escaped = existing_text.gsub("\n","\\n")
|
687
|
+
launcher.automator.enter_text_with_keyboard(text, escaped)
|
391
688
|
end
|
392
689
|
|
393
690
|
# @!visibility private
|
394
|
-
#
|
395
|
-
|
396
|
-
|
397
|
-
|
691
|
+
#
|
692
|
+
# Enters text into view identified by a query
|
693
|
+
#
|
694
|
+
# This behavior of this method depends on the Gesture::Performer
|
695
|
+
# implementation.
|
696
|
+
#
|
697
|
+
# ### UIAutomation
|
698
|
+
#
|
699
|
+
# defaults to calling 'setValue' in UIAutomation on the UITextField or
|
700
|
+
# UITextView. This is fast, but in some cases might result in slightly
|
701
|
+
# different behaviour than using `keyboard_enter_text`.
|
702
|
+
# To force use of #keyboard_enter_text option :use_keyboard
|
703
|
+
#
|
704
|
+
# ### DeviceAgent
|
705
|
+
#
|
706
|
+
# This method calls #keyboard_enter_text regardless of the options passed.
|
707
|
+
#
|
708
|
+
# @param [String] uiquery the element to enter text into
|
709
|
+
# @param [String] text the text to enter
|
710
|
+
# @param [Hash] options controls details of text entry
|
711
|
+
# @option options [Boolean] :use_keyboard (false) use the iOS keyboard
|
712
|
+
# to enter each character separately
|
713
|
+
# @option options [Boolean] :wait (true) call wait_for_element_exists with
|
714
|
+
# uiquery
|
715
|
+
# @option options [Hash] :wait_options ({}) if :wait pass this as options
|
716
|
+
# to wait_for_element_exists
|
717
|
+
def enter_text_in(uiquery, text, options = {})
|
718
|
+
default_opts = {:use_keyboard => false, :wait => true, :wait_options => {}}
|
719
|
+
options = default_opts.merge(options)
|
720
|
+
wait_for_element_exists(uiquery, options[:wait_options]) if options[:wait]
|
721
|
+
touch(uiquery, options)
|
722
|
+
wait_for_keyboard
|
723
|
+
if options[:use_keyboard]
|
724
|
+
keyboard_enter_text(text)
|
725
|
+
else
|
726
|
+
fast_enter_text(text)
|
398
727
|
end
|
399
|
-
|
728
|
+
end
|
729
|
+
|
730
|
+
alias_method :enter_text, :enter_text_in
|
731
|
+
|
732
|
+
# @!visibility private
|
733
|
+
#
|
734
|
+
# Enters text into current text input field
|
735
|
+
#
|
736
|
+
# This behavior of this method depends on the Gesture::Performer
|
737
|
+
# implementation.
|
738
|
+
#
|
739
|
+
# ### UIAutomation
|
740
|
+
#
|
741
|
+
# defaults to calling 'setValue' in UIAutomation on the UITextField or
|
742
|
+
# UITextView. This is fast, but in some cases might result in slightly
|
743
|
+
# different behaviour than using `keyboard_enter_text`.
|
744
|
+
# To force use of #keyboard_enter_text option :use_keyboard
|
745
|
+
#
|
746
|
+
# ### DeviceAgent
|
747
|
+
#
|
748
|
+
# This method calls #keyboard_enter_text.
|
749
|
+
#
|
750
|
+
# @param [String] text the text to enter
|
751
|
+
def fast_enter_text(text)
|
752
|
+
expect_keyboard_visible!
|
753
|
+
launcher.automator.fast_enter_text(text)
|
754
|
+
end
|
755
|
+
|
756
|
+
# Dismisses a iPad keyboard by touching the 'Hide keyboard' button and waits
|
757
|
+
# for the keyboard to disappear.
|
758
|
+
#
|
759
|
+
# @note
|
760
|
+
# the dismiss keyboard key does not exist on the iPhone or iPod
|
761
|
+
#
|
762
|
+
# @raise [RuntimeError] If the device is not an iPad
|
763
|
+
# @raise [Calabash::Cucumber::WaitHelpers::WaitError] If the keyboard does
|
764
|
+
# not disappear.
|
765
|
+
def dismiss_ipad_keyboard
|
766
|
+
# TODO Maybe relax this restriction; turn it into a nop on iPhones?
|
767
|
+
# TODO Support iPhone 6 Plus form factor dismiss keyboard key.
|
768
|
+
if device_family_iphone?
|
769
|
+
screenshot_and_raise %Q[
|
770
|
+
There is no Hide Keyboard key on an iPhone.
|
771
|
+
|
772
|
+
Use `ipad?` to branch in your test.
|
773
|
+
|
774
|
+
]
|
775
|
+
end
|
776
|
+
|
777
|
+
expect_keyboard_visible!
|
778
|
+
|
779
|
+
launcher.automator.dismiss_ipad_keyboard
|
780
|
+
|
781
|
+
wait_for_no_keyboard
|
400
782
|
end
|
401
783
|
|
402
784
|
# Scroll a scroll view in a direction. By default scrolls half the frame size.
|
@@ -657,7 +1039,7 @@ module Calabash
|
|
657
1039
|
# Sends the app to the background.
|
658
1040
|
#
|
659
1041
|
# Sending the app to the background for more than 60 seconds may
|
660
|
-
# cause
|
1042
|
+
# cause unpredictable results.
|
661
1043
|
#
|
662
1044
|
# @param [Numeric] seconds How long to send the app to the background.
|
663
1045
|
# @raise [ArgumentError] if `seconds` argument is < 1.0
|
@@ -915,9 +1297,20 @@ arguments => '#{arguments}'
|
|
915
1297
|
:exit_code => merged_opts[:exit_code]
|
916
1298
|
}
|
917
1299
|
)
|
918
|
-
|
1300
|
+
|
1301
|
+
rescue Errno::ECONNREFUSED, HTTPClient::KeepAliveDisconnected, SocketError
|
919
1302
|
[]
|
920
1303
|
end
|
1304
|
+
|
1305
|
+
if launcher.automator
|
1306
|
+
if launcher.automator.name == :device_agent
|
1307
|
+
delay = merged_opts[:post_resign_active_delay] +
|
1308
|
+
merged_opts[:post_will_terminate_delay] + 0.4
|
1309
|
+
sleep(delay)
|
1310
|
+
launcher.automator.send(:session_delete)
|
1311
|
+
end
|
1312
|
+
end
|
1313
|
+
true
|
921
1314
|
end
|
922
1315
|
|
923
1316
|
# Get the Calabash server log level.
|
@@ -953,11 +1346,14 @@ arguments => '#{arguments}'
|
|
953
1346
|
launcher
|
954
1347
|
end
|
955
1348
|
|
956
|
-
# Helper method to easily create page object instances from a cucumber
|
957
|
-
#
|
958
|
-
#
|
959
|
-
#
|
960
|
-
#
|
1349
|
+
# Helper method to easily create page object instances from a cucumber
|
1350
|
+
# execution context.
|
1351
|
+
#
|
1352
|
+
# The advantage of using `page` to instantiate a page object class is that
|
1353
|
+
# it will automatically store a reference to the current Cucumber world
|
1354
|
+
# which is needed in the page object methods to call Cucumber-specific
|
1355
|
+
# methods like puts or embed.
|
1356
|
+
#
|
961
1357
|
# @example Instantiating a `LoginPage` from a step definition
|
962
1358
|
# Given(/^I am about to login to a self-hosted site$/) do
|
963
1359
|
# @current_page = page(LoginPage).await(timeout: 30)
|
@@ -965,9 +1361,12 @@ arguments => '#{arguments}'
|
|
965
1361
|
# end
|
966
1362
|
#
|
967
1363
|
# @see Calabash::IBase
|
968
|
-
# @param {Class} clz the page object class to instantiate (passing the
|
969
|
-
#
|
970
|
-
# @
|
1364
|
+
# @param {Class} clz the page object class to instantiate (passing the
|
1365
|
+
# Cucumber world and `args`)
|
1366
|
+
# @param {Array} args optional additional arguments to pass to the page
|
1367
|
+
# object constructor
|
1368
|
+
# @return {Object} a fresh instance of `Class clz` which has been passed
|
1369
|
+
# a reference to the cucumber World object.
|
971
1370
|
def page(clz,*args)
|
972
1371
|
clz.new(self,*args)
|
973
1372
|
end
|
@@ -1181,42 +1580,137 @@ arguments => '#{arguments}'
|
|
1181
1580
|
launcher.attach({:uia_strategy => uia_strategy})
|
1182
1581
|
end
|
1183
1582
|
|
1583
|
+
# Returns an object that provides an interface to the DeviceAgent
|
1584
|
+
# public query and gesture API.
|
1585
|
+
#
|
1586
|
+
# @see Calabash::Cucumber::DeviceAgent
|
1587
|
+
#
|
1588
|
+
# @example
|
1589
|
+
# device_agent.query({marked: "Cancel"})
|
1590
|
+
# device_agent.touch({marked: "Cancel"})
|
1591
|
+
#
|
1592
|
+
# @return [Calabash::Cucumber::DeviceAgent]
|
1593
|
+
#
|
1594
|
+
# @raise [RuntimeError] If the application has not been launched.
|
1595
|
+
# @raise [RuntimeError] If there is no automator attached to the
|
1596
|
+
# current launcher
|
1597
|
+
# @raise [RuntimeError] If the automator attached the current
|
1598
|
+
# launcher is not DeviceAgent
|
1599
|
+
# @raise [RuntimeError] If the automator is not running.
|
1600
|
+
def device_agent
|
1601
|
+
launcher = Calabash::Cucumber::Launcher.launcher_if_used
|
1602
|
+
if !launcher
|
1603
|
+
raise RuntimeError, %Q[
|
1604
|
+
There is no launcher.
|
1605
|
+
|
1606
|
+
If you are in the Calabash console, you can try to attach to an already running
|
1607
|
+
Calabash test using:
|
1608
|
+
|
1609
|
+
> console_attach
|
1610
|
+
|
1611
|
+
If you are running from Cucumber or rspec, call Launcher#relaunch before calling
|
1612
|
+
this method.
|
1613
|
+
|
1614
|
+
]
|
1615
|
+
end
|
1616
|
+
|
1617
|
+
if !launcher.automator
|
1618
|
+
raise RuntimeError, %Q[
|
1619
|
+
The launcher is not attached to an automator.
|
1620
|
+
|
1621
|
+
If you are in the Calabash console, you can try to attach to an already running
|
1622
|
+
Calabash test using:
|
1623
|
+
|
1624
|
+
> console_attach
|
1625
|
+
|
1626
|
+
If you are running from Cucumber or rspec, call Launcher#relaunch before calling
|
1627
|
+
this method.
|
1628
|
+
|
1629
|
+
]
|
1630
|
+
end
|
1631
|
+
|
1632
|
+
if launcher.automator.name != :device_agent
|
1633
|
+
raise RuntimeError, %Q[
|
1634
|
+
The launcher automator is not DeviceAgent:
|
1635
|
+
|
1636
|
+
#{launcher.automator}
|
1637
|
+
|
1638
|
+
#device_agent is only available for Xcode 8.
|
1639
|
+
|
1640
|
+
In your tests, use this pattern to branch on the availability of DeviceAgent.
|
1641
|
+
|
1642
|
+
if uia_available?
|
1643
|
+
# Make a UIA call
|
1644
|
+
else
|
1645
|
+
# Make a DeviceAgent call
|
1646
|
+
end
|
1647
|
+
|
1648
|
+
]
|
1649
|
+
end
|
1650
|
+
automator = launcher.automator
|
1651
|
+
|
1652
|
+
if !automator.running?
|
1653
|
+
raise RuntimeError, %Q[The DeviceAgent is not running.]
|
1654
|
+
else
|
1655
|
+
require "calabash-cucumber/device_agent"
|
1656
|
+
Calabash::Cucumber::DeviceAgent.new(automator.client, self)
|
1657
|
+
end
|
1658
|
+
end
|
1659
|
+
|
1184
1660
|
# @!visibility private
|
1661
|
+
# TODO should be private
|
1185
1662
|
def launcher
|
1186
1663
|
Calabash::Cucumber::Launcher.launcher
|
1187
1664
|
end
|
1188
1665
|
|
1189
1666
|
# @!visibility private
|
1667
|
+
# TODO should be private
|
1190
1668
|
def run_loop
|
1191
|
-
|
1192
|
-
|
1669
|
+
launcher = Calabash::Cucumber::Launcher.launcher_if_used
|
1670
|
+
if launcher
|
1671
|
+
launcher.run_loop
|
1672
|
+
else
|
1673
|
+
nil
|
1674
|
+
end
|
1193
1675
|
end
|
1194
1676
|
|
1195
1677
|
# @!visibility private
|
1196
1678
|
def tail_run_loop_log
|
1197
|
-
|
1198
|
-
|
1199
|
-
|
1679
|
+
if !run_loop
|
1680
|
+
raise "Unable to tail instruments log because there is no active run-loop"
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
require "calabash-cucumber/log_tailer"
|
1684
|
+
|
1685
|
+
if launcher.instruments?
|
1686
|
+
Calabash::Cucumber::LogTailer.tail_in_terminal(run_loop[:log_file])
|
1687
|
+
else
|
1688
|
+
# TODO Tail the .run_loop/xcuitest/<launcher>.log?
|
1689
|
+
raise "Cannot tail a non-instruments run-loop"
|
1200
1690
|
end
|
1201
|
-
cmd = %Q[osascript -e 'tell application "Terminal" to do script "tail -n 10000 -f #{l[:log_file]} | grep -v \\"Default: \\\\*\\""']
|
1202
|
-
raise "Unable to " unless system(cmd)
|
1203
1691
|
end
|
1204
1692
|
|
1205
1693
|
# @!visibility private
|
1206
1694
|
def dump_run_loop_log
|
1207
|
-
|
1208
|
-
|
1209
|
-
raise 'Unable to dump run_loop since there is not active run_loop...'
|
1695
|
+
if !run_loop
|
1696
|
+
raise "Unable to dump run-loop log because there is no active run-loop"
|
1210
1697
|
end
|
1211
|
-
cmd = %Q[cat "#{l[:log_file]}" | grep -v "Default: \\*\\*\\*"]
|
1212
|
-
puts `#{cmd}`
|
1213
|
-
end
|
1214
1698
|
|
1699
|
+
if launcher.instruments?
|
1700
|
+
cmd = %Q[cat "#{run_loop[:log_file]}" | grep -v "Default: \\*\\*\\*"]
|
1701
|
+
RunLoop.log_unix_cmd(cmd)
|
1702
|
+
puts `#{cmd}`
|
1703
|
+
true
|
1704
|
+
else
|
1705
|
+
# TODO What should we dump in non-instruments runs?
|
1706
|
+
raise "Cannot dump non-instruments run-loop"
|
1707
|
+
end
|
1708
|
+
end
|
1215
1709
|
|
1216
1710
|
# @!visibility private
|
1217
1711
|
def query_action_with_options(action, uiquery, options)
|
1218
1712
|
uiquery, options = extract_query_and_options(uiquery, options)
|
1219
|
-
views_touched = launcher.
|
1713
|
+
views_touched = launcher.automator.send(action, options)
|
1220
1714
|
unless uiquery.nil?
|
1221
1715
|
msg = "#{action} could not find view: '#{uiquery}', args: #{options}"
|
1222
1716
|
Map.assert_map_results(views_touched, msg)
|