calabash-cucumber 0.20.0 → 0.20.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3beda8dc174cf33416893af2d2e58ed4a18d58cb
4
- data.tar.gz: 60902d11897fa82bccba3e46aa869b52f152b9cc
3
+ metadata.gz: 5d80bc583ef0ea5ae3fb6548acd4d5ced7f15b2c
4
+ data.tar.gz: 61431194a8a83d91b41674c2d5ec6f83076b92fa
5
5
  SHA512:
6
- metadata.gz: 734473873f98af082e74350e287571804aacb5855adff80bec317eb5cc73083ee6f7da6055f58aba53b3a9e15e9b5a775434febd7dfa18c5785ae89203958d87
7
- data.tar.gz: 033948ff96011ee84c325adad1d72ceeef5fb4e22fdc607cb88e725ff19a20a17c6c6f7c6ba5c011054ae4f06a2c88e62993bf0eb6122127374615928610d176
6
+ metadata.gz: 1b9e3bbb6993f444dfd6a9e83e8e4de9d9c2b48080a820c71032967755366eb984de455bac2bfce9fe18a823050a482c8cdf937c56e1e8ae6cfc592b8047fcc8
7
+ data.tar.gz: c25c2390ba6e3b3542bfa5e5a1c5cad9dfc6ad73fea899405f2aacc8b25d09b817a3f92dee5a1285cd8a67044ee7b189730bb67c20fc8c9c439c115c6bb1a0af
Binary file
@@ -34,6 +34,28 @@ module Calabash
34
34
  abstract_method!
35
35
  end
36
36
 
37
+ # @!visibility private
38
+ #
39
+ # This code is redundant. It would be easy to pass the Launcher
40
+ # device instance to the automator, but that would require an XTC patch.
41
+ #
42
+ # This code is also duplicated in the EnvironmentHelpers.
43
+ #
44
+ # We need the device screen size to support full-screen pan gestures.
45
+ #
46
+ # Asking for the top-most view is not good enough and asking for the
47
+ # largest UIWindow is not specific enough (map apps have a huge window).
48
+ def device
49
+ @device ||= begin
50
+ require "calabash-cucumber/http/http"
51
+ require "calabash-cucumber/environment"
52
+ require "calabash-cucumber/device"
53
+ _, body = Calabash::Cucumber::HTTP.ensure_connectivity
54
+ endpoint = Calabash::Cucumber::Environment.device_endpoint
55
+ Calabash::Cucumber::Device.new(endpoint, body)
56
+ end
57
+ end
58
+
37
59
  # @!visibility private
38
60
  def touch(options)
39
61
  abstract_method!
@@ -79,6 +101,8 @@ module Calabash
79
101
  end
80
102
 
81
103
  # @!visibility private
104
+ #
105
+ # Callers must validate the options.
82
106
  def pinch(in_or_out, options)
83
107
  abstract_method!
84
108
  end
@@ -130,13 +130,14 @@ args[0] = #{args[0]}])
130
130
  dupped_options = options.dup
131
131
 
132
132
  if dupped_options[:query].nil?
133
- dupped_options[:query] = "*"
133
+ element = element_for_device_screen
134
+ from_point = point_from(element, options)
135
+ else
136
+ hash = query_for_coordinates(dupped_options)
137
+ from_point = hash[:coordinates]
138
+ element = hash[:view]
134
139
  end
135
140
 
136
- hash = query_for_coordinates(dupped_options)
137
- from_point = hash[:coordinates]
138
- element = hash[:view]
139
-
140
141
  # DeviceAgent does not understand the :force. Does anyone?
141
142
  force = dupped_options[:force]
142
143
  case force
@@ -158,7 +159,38 @@ args[0] = #{args[0]}])
158
159
  direction = dupped_options[:direction]
159
160
  to_point = Coordinates.end_point_for_swipe(direction, element, force)
160
161
  client.pan_between_coordinates(from_point, to_point, gesture_options)
161
- [hash[:view]]
162
+ [element]
163
+ end
164
+
165
+ # @!visibility private
166
+ def pinch(in_out, options)
167
+ dupped_options = options.dup
168
+
169
+ if dupped_options[:query].nil?
170
+ element = element_for_device_screen
171
+ coordinates = point_from(element, options)
172
+ else
173
+ hash = query_for_coordinates(dupped_options)
174
+ element = hash[:view]
175
+ coordinates = hash[:coordinates]
176
+ end
177
+
178
+ in_out = in_out.to_s
179
+ duration = dupped_options[:duration]
180
+ amount = dupped_options[:amount]
181
+
182
+ gesture_options = {
183
+ :pinch_direction => in_out,
184
+ :amount => amount,
185
+ :duration => duration
186
+ }
187
+
188
+ client.perform_coordinate_gesture("pinch",
189
+ coordinates[:x],
190
+ coordinates[:y],
191
+ gesture_options)
192
+
193
+ [element]
162
194
  end
163
195
 
164
196
  # @!visibility private
@@ -223,12 +255,12 @@ args[0] = #{args[0]}])
223
255
 
224
256
  # @!visibility private
225
257
  def enter_text_with_keyboard(string, options={})
226
- client.enter_text(string)
258
+ client.enter_text_without_keyboard_check(string)
227
259
  end
228
260
 
229
261
  # @!visibility private
230
262
  def enter_char_with_keyboard(char)
231
- client.enter_text(char)
263
+ client.enter_text_without_keyboard_check(char)
232
264
  end
233
265
 
234
266
  # @!visibility private
@@ -242,7 +274,7 @@ args[0] = #{args[0]}])
242
274
  if mark
243
275
  begin
244
276
  # The underlying query for coordinates always expects results.
245
- value = client.touch({marked: mark})
277
+ value = client.touch({type: "Button", marked: mark})
246
278
  return value
247
279
  rescue RuntimeError => _
248
280
  RunLoop.log_debug("Cannot find mark '#{mark}' with query; will send a newline")
@@ -252,7 +284,7 @@ args[0] = #{args[0]}])
252
284
  end
253
285
 
254
286
  code = char_for_keyboard_action("Return")
255
- client.enter_text(code)
287
+ client.enter_text_without_keyboard_check(code)
256
288
  end
257
289
 
258
290
  # @!visibility private
@@ -262,7 +294,7 @@ args[0] = #{args[0]}])
262
294
 
263
295
  # @!visibility private
264
296
  def fast_enter_text(text)
265
- client.enter_text(text)
297
+ client.enter_text_without_keyboard_check(text)
266
298
  end
267
299
 
268
300
  # @!visibility private
@@ -353,6 +385,27 @@ Make sure your query returns at least one view.
353
385
  end
354
386
  end
355
387
 
388
+ # @!visibility private
389
+ def element_for_device_screen
390
+ screen_dimensions = device.screen_dimensions
391
+
392
+ scale = screen_dimensions[:scale]
393
+ height = (screen_dimensions[:height]/scale).to_i
394
+ center_y = (height/2)
395
+ width = (screen_dimensions[:width]/scale).to_i
396
+ center_x = (width/2)
397
+
398
+ {
399
+ "screen" => true,
400
+ "rect" => {
401
+ "height" => height,
402
+ "width" => width,
403
+ "center_x" => center_x,
404
+ "center_y" => center_y
405
+ }
406
+ }
407
+ end
408
+
356
409
  # @!visibility private
357
410
  #
358
411
  # Don't change the double quotes.
@@ -400,17 +453,25 @@ Make sure your query returns at least one view.
400
453
  # @!visibility private
401
454
  def return_key_type_of_first_responder
402
455
 
403
- ['textField', 'textView'].each do |ui_class|
404
- query = "#{ui_class} isFirstResponder:1"
405
- raw = Calabash::Cucumber::Map.raw_map(query, :query, :returnKeyType)
406
- results = raw["results"]
407
- if !results.empty?
408
- return results.first
409
- end
456
+ query = "* isFirstResponder:1"
457
+ raw = Calabash::Cucumber::Map.raw_map(query, :query, :returnKeyType)
458
+ elements = raw["results"]
459
+ return nil if elements.count == 0
460
+
461
+ return_key_type = elements[0]
462
+
463
+ # first responder did not respond to :text selector
464
+ if return_key_type == "*****"
465
+ RunLoop.log_debug("First responder does not respond to :returnKeyType")
466
+ return nil
467
+ end
468
+
469
+ if return_key_type.nil?
470
+ RunLoop.log_debug("First responder has nil :returnKeyType")
471
+ return nil
410
472
  end
411
473
 
412
- RunLoop.log_debug("Cannot find keyboard first responder to ask for its returnKeyType")
413
- nil
474
+ return_key_type
414
475
  end
415
476
 
416
477
  # @!visibility private
@@ -102,7 +102,9 @@ module Calabash
102
102
  "Uti, non abuti.",
103
103
  "Non Satis Scire",
104
104
  "Nullius in verba",
105
- "Det ka æn jå væer ei jált"
105
+ "Det ka æn jå væer ei jált",
106
+ "Dzień dobry",
107
+ "Jestem tu by ocalić świat"
106
108
  ]
107
109
  puts RunLoop::Color.green("Calabash says, \"#{messages.shuffle.first}\"")
108
110
  end
@@ -148,6 +150,19 @@ module Calabash
148
150
  puts ""
149
151
  end
150
152
 
153
+ # @!visibility private
154
+ # Do not call this method directly.
155
+ def _try_to_attach
156
+ begin
157
+ Calabash::Cucumber::HTTP.ping_app
158
+ launcher = Calabash::Cucumber::Launcher.new
159
+ launcher.attach
160
+ puts(RunLoop::Color.green("Attached to: #{launcher}"))
161
+ launcher
162
+ rescue => _
163
+ end
164
+ end
165
+
151
166
  private
152
167
 
153
168
  # List the visible element with given mark(s).
@@ -382,13 +382,13 @@ Expected: options[:offset] = {:x => NUMERIC, :y => NUMERIC}
382
382
  # @option options {Hash} :offset (nil) optional offset to touch point.
383
383
  # Offset supports an `:x` and `:y` key and causes the touch to be
384
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
385
  # @option options [Symbol] :force (normal) Indicates the force of the
389
386
  # swipe. Valid values are :strong, :normal, :light.
387
+ # @option options {String} :query (nil) If specified, the swipe will be
388
+ # made on the first view matching this query. If this option is nil
389
+ # (the default), the swipe will happen at the center of the screen.
390
390
  #
391
- # @return {Array<Hash>,String} An array with one element; the view that
391
+ # @return {Array<Hash>} An array with one element; the view that
392
392
  # was swiped.
393
393
  #
394
394
  # @raise [ArgumentError] If :force is invalid.
@@ -584,20 +584,70 @@ The minimum duration is 0.0.
584
584
  end
585
585
 
586
586
  # Performs a "pinch" gesture.
587
+ #
587
588
  # By default, the gesture starts at the center of the screen.
588
- # @todo `pinch` is an old style API which doesn't take a query as its first argument. We should migrate this.
589
+ #
589
590
  # @example
591
+ # # Zoom in
590
592
  # pinch :out
591
- # @example
593
+ #
594
+ # # Zoom out
592
595
  # pinch :in, query:"MKMapView", offset:{x:42}
593
- # @param {String} in_out the direction to pinch ('in' or 'out') (symbols can also be used).
596
+ #
597
+ # @param {String, Symbol} in_out the direction to pinch ('in' or 'out')
594
598
  # @param {Hash} options option for modifying the details of the touch.
595
- # @option options {Hash} :offset (nil) optional offset to touch point. Offset supports an `:x` and `:y` key
596
- # and causes the touch to be offset with `(x,y)` relative to the center (`center + (offset[:x], offset[:y])`).
597
- # @option options {String} :query (nil) if specified, the pinch will be made relative to this query.
598
- # @return {Array<Hash>,String} array containing the serialized version of the touched view if `options[:query]` is given.
599
+ # @option options {Hash} :offset (nil) optional offset to touch point.
600
+ # @option options {String} :query (nil) The view to pinch on. If this
601
+ # value is nil, the pinch happens at the center of the screen.
602
+ # @option options {Numeric} :amount (100) How large (in points) the
603
+ # pinch should be. This option is ignored when running with UIAutomation.
604
+ # @option options {Numeric} :duration (1.0) duration of the 'pinch'. The
605
+ # minimum value of pan in UIAutomation is 0.5. For DeviceAgent, the
606
+ # duration must be > 0.
607
+ # @return {Array<Hash>} array containing the serialized version of
608
+ # the view touched.
609
+ #
610
+ # @raise [ArgumentError] If duration is < 0.5 for UIAutomation and <= 0
611
+ # for DeviceAgent.
612
+ # @raise [ArgumentError] If in_out argument is invalid.
599
613
  def pinch(in_out, options={})
600
- launcher.automator.pinch(in_out.to_sym, options)
614
+ merged_options = {
615
+ :query => nil,
616
+ # Ignored by UIAutomation
617
+ :amount => 100,
618
+ :duration => 0.5
619
+ }.merge(options)
620
+
621
+ symbol = in_out.to_sym
622
+
623
+ if ![:in, :out].include?(symbol)
624
+ raise ArgumentError, %Q[
625
+ Invalid pinch direction: '#{symbol}'. Valid directions are:
626
+
627
+ "in", "out", :in, :out
628
+
629
+ ]
630
+ end
631
+
632
+ duration = merged_options[:duration]
633
+
634
+ if uia_available? && duration < 0.5
635
+ raise ArgumentError, %Q[
636
+ Invalid duration: #{duration}
637
+
638
+ The minimum duration is 0.5
639
+
640
+ ]
641
+ elsif duration <= 0.0
642
+ raise ArgumentError, %Q[
643
+ Invalid duration: #{duration}
644
+
645
+ The minimum duration is 0.0.
646
+
647
+ ]
648
+ end
649
+
650
+ launcher.automator.pinch(in_out.to_sym, merged_options)
601
651
  end
602
652
 
603
653
  # @deprecated 0.21.0 Use #keyboard_enter_text
@@ -757,7 +807,7 @@ To type strings with more than one character, use keyboard_enter_text.
757
807
  # for the keyboard to disappear.
758
808
  #
759
809
  # @note
760
- # the dismiss keyboard key does not exist on the iPhone or iPod
810
+ # The dismiss keyboard key does not exist on the iPhone or iPod
761
811
  #
762
812
  # @raise [RuntimeError] If the device is not an iPad
763
813
  # @raise [Calabash::Cucumber::WaitHelpers::WaitError] If the keyboard does
@@ -802,38 +852,111 @@ Use `ipad?` to branch in your test.
802
852
  views_touched
803
853
  end
804
854
 
855
+ # Scrolls to a mark in a UIScrollView.
856
+ #
857
+ # Make sure your query matches exactly one UIScrollView. If multiple
858
+ # scroll views are matched, the results can be unpredictable.
859
+ #
860
+ # @example
861
+ # scroll_to_mark("settings")
862
+ # scroll_to_mark("Android", {:animated => false})
863
+ # scroll_to_mark("Alarm", {:query => "UIScrollView marked:'Settings'"})
864
+ #
865
+ # @see #scroll_to_row_with_mark
866
+ # @see #scroll_to_collection_view_item_with_mark
867
+ #
868
+ # @param [String] mark an accessibility label or identifier or text
869
+ # @param [Hash] options controls the query and and scroll behavior
870
+ # @option options [String] :query ("UIScrollView index:0") A query to
871
+ # uniquely identify the scroll view if there are multiple scroll views.
872
+ # @option options [Boolean] :animate (true) should the scrolling be animated
873
+ # @option options [String] :failure_message (nil) If nil, a default failure
874
+ # message will be shown if this scroll scroll cannot be performed.
875
+ #
876
+ # @raise [RuntimeError] If the scroll cannot be performed
877
+ # @raise [RuntimeError] If the :query finds no scroll view
878
+ # @raise [ArgumentError] If the mark is nil
879
+ # @raise [ArgumentError] If the :query value is nil, "", or "*".
880
+ def scroll_to_mark(mark, options={})
881
+ if mark.nil?
882
+ raise ArgumentError, "The mark cannot be nil"
883
+ end
884
+
885
+ merged_options = {:query => "UIScrollView index:0",
886
+ :animate => true,
887
+ :failure_message => nil}.merge(options)
888
+
889
+ uiquery = merged_options[:query]
890
+
891
+ if uiquery.nil?
892
+ raise ArgumentError, "The :query option cannot be nil"
893
+ end
894
+
895
+ if uiquery == ""
896
+ raise ArgumentError, "The :query option cannot be the empty string"
897
+ end
898
+
899
+ if uiquery == "*"
900
+ raise ArgumentError, "The :query option cannot be the wildcard '*'"
901
+ end
902
+
903
+ args = [merged_options[:animate]]
904
+
905
+ views_touched = Map.map(uiquery, :scrollToMark, mark, *args)
906
+
907
+ message = merged_options[:failure_message]
908
+
909
+ if !message
910
+ message = %Q[
911
+
912
+ Unable to scroll to mark '#{mark}' in UIScrollView matching #{uiquery}"
913
+
914
+ ]
915
+ end
916
+
917
+ Map.assert_map_results(views_touched, message)
918
+ views_touched
919
+ end
920
+
805
921
  # Scroll a table view to a row. Table view should have only one section.
922
+ #
923
+ # Make sure your query matches exactly one UITableView. If multiple views
924
+ # are matched, the results can be unpredictable.
925
+ #
806
926
  # @see #scroll_to_cell
927
+ #
807
928
  # @example
808
- # scroll_to_row "UITableView", 2
809
- # @note this is implemented by calling the Obj-C `scrollToRowAtIndexPath:atScrollPosition:animated:` method
810
- # and can do things users cant.
929
+ # scroll_to_row "UITableView index:0", 2
811
930
  #
812
- # @param {String} uiquery query describing view scroll (should be UIScrollView or a web view).
931
+ # @param {String} uiquery Should match a UITableView
813
932
  def scroll_to_row(uiquery, number)
814
933
  views_touched = Map.map(uiquery, :scrollToRow, number)
815
- msg = "unable to scroll: '#{uiquery}' to: #{number}"
934
+ msg = "Unable to scroll to row #{number} in table view with '#{uiquery}'"
816
935
  Map.assert_map_results(views_touched, msg)
817
936
  views_touched
818
937
  end
819
938
 
820
- # Scroll a table view to a section and row. Table view can have multiple sections.
939
+ # Scroll a table view to a section and row.
940
+ #
941
+ # Make sure your query matches exactly one UITableView. If multiple views
942
+ # are matched, the results can be unpredictable.
821
943
  #
822
944
  # @todo should expose a non-option first argument query and required parameters `section`, `row`
823
945
  #
824
946
  # @see #scroll_to_row
825
947
  # @example
826
- # scroll_to_cell query:"UITableView", row:4, section:0, animate: false
827
- # @note this is implemented by calling the Obj-C `scrollToRowAtIndexPath:atScrollPosition:animated:` method
828
- # and can do things users cant.
948
+ # scroll_to_cell row:4, section:0, animate: false
829
949
  #
830
950
  # @param {Hash} options specifies details of the scroll
831
- # @option options {String} :query ('tableView') query specifying which table view to scroll
951
+ # @option options {String} :query ("UITableView index:0") query specifying
952
+ # which table view to scroll
832
953
  # @option options {Fixnum} :section section to scroll to
833
954
  # @option options {Fixnum} :row row to scroll to
834
955
  # @option options {String} :scroll_position position to scroll to
835
956
  # @option options {Boolean} :animated (true) animate or not
836
- def scroll_to_cell(options={:query => 'tableView',
957
+ # @raise [ArgumentError] If row or section is nil
958
+ # @raise [ArgumentError] If the :query value is nil, "", or "*".
959
+ def scroll_to_cell(options={:query => "UITableView index:0",
837
960
  :row => 0,
838
961
  :section => 0,
839
962
  :scroll_position => :top,
@@ -841,8 +964,8 @@ Use `ipad?` to branch in your test.
841
964
  uiquery = options[:query] || 'tableView'
842
965
  row = options[:row]
843
966
  sec = options[:section]
844
- if row.nil? or sec.nil?
845
- raise 'You must supply both :row and :section keys to scroll_to_cell'
967
+ if row.nil? || sec.nil?
968
+ raise ArgumentError, 'You must supply both :row and :section keys to scroll_to_cell'
846
969
  end
847
970
 
848
971
  args = []
@@ -862,14 +985,16 @@ Use `ipad?` to branch in your test.
862
985
 
863
986
  # Scrolls to a mark in a UITableView.
864
987
  #
988
+ # Make sure your query matches exactly one UITableView. If multiple
989
+ # views are matched, the results can be unpredictable.
990
+ #
865
991
  # @example Scroll to the top of the item with the given mark.
866
992
  # scroll_to_row_with_mark('settings', {:scroll_position => :top})
867
993
  #
868
994
  # @example Scroll to the bottom of the item with the given mark.
869
995
  # scroll_to_row_with_mark('about', {:scroll_position => :bottom})
870
996
  #
871
- # @param [String] mark an accessibility `{label | identifier}` or text in
872
- # or on the row
997
+ # @param [String] mark an accessibility label or identifier or text in row
873
998
  # @param [Hash] options controls the query and and scroll behavior
874
999
  #
875
1000
  # @option options [String] :query ('tableView')
@@ -879,38 +1004,64 @@ Use `ipad?` to branch in your test.
879
1004
  # `{:middle | :top | :bottom}`
880
1005
  # @option options [Boolean] :animate (true)
881
1006
  # should the scrolling be animated
1007
+ # @option options [String] :failure_message (nil) If nil, a default failure
1008
+ # message will be shown if this scroll scroll cannot be performed.
882
1009
  #
883
1010
  # @raise [RuntimeError] if the scroll cannot be performed
884
- # @raise [RuntimeError] if the mark is nil
885
1011
  # @raise [RuntimeError] if the table query finds no table view
886
1012
  # @raise [RuntimeError] if the scroll position is invalid
887
- def scroll_to_row_with_mark(mark, options={:query => 'tableView',
888
- :scroll_position => :middle,
889
- :animate => true})
1013
+ # @raise [ArgumentError] if the mark is nil
1014
+ # @raise [ArgumentError] If the :query value is nil, "", or "*".
1015
+ def scroll_to_row_with_mark(mark, options={})
1016
+ merged_options = {:query => "UITableView index:0",
1017
+ :scroll_position => :middle,
1018
+ :animate => true,
1019
+ :failure_message => nil}.merge(options)
1020
+
890
1021
  if mark.nil?
891
- screenshot_and_raise 'mark argument cannot be nil'
1022
+ raise ArgumentError, "The mark cannot be nil"
892
1023
  end
893
1024
 
894
- uiquery = options[:query] || 'tableView'
1025
+ uiquery = merged_options[:query]
895
1026
 
896
- args = []
897
- if options.has_key?(:scroll_position)
898
- args << options[:scroll_position]
899
- else
900
- args << 'middle'
1027
+ if uiquery.nil?
1028
+ raise ArgumentError, "The :query option cannot be nil"
901
1029
  end
902
- if options.has_key?(:animate)
903
- args << options[:animate]
1030
+
1031
+ if uiquery == ""
1032
+ raise ArgumentError, "The :query option cannot be the empty string"
904
1033
  end
905
1034
 
1035
+ if uiquery == "*"
1036
+ raise ArgumentError, "The :query option cannot be the wildcard '*'"
1037
+ end
1038
+
1039
+ args = [merged_options[:scroll_position], merged_options[:animate]]
1040
+
906
1041
  views_touched = Map.map(uiquery, :scrollToRowWithMark, mark, *args)
907
- msg = options[:failed_message] || "Unable to scroll: '#{uiquery}' to: #{options}"
908
- Map.assert_map_results(views_touched, msg)
1042
+
1043
+ message = merged_options[:failure_message]
1044
+ if !message
1045
+ message = %Q[
1046
+ Unable to scroll to mark: '#{mark}' in table view matched by query:
1047
+
1048
+ #{uiquery}
1049
+
1050
+ with options:
1051
+
1052
+ #{merged_options}
1053
+
1054
+ ]
1055
+ end
1056
+ Map.assert_map_results(views_touched, message)
909
1057
  views_touched
910
1058
  end
911
1059
 
912
1060
  # Scrolls to an item in a section of a UICollectionView.
913
1061
  #
1062
+ # Make sure your query matches exactly one UICollectionView. If multiple
1063
+ # views are matched, the results can be unpredictable.
1064
+ #
914
1065
  # @note item and section are zero-indexed
915
1066
  #
916
1067
  # @example Scroll to item 0 in section 2 to top.
@@ -922,12 +1073,12 @@ Use `ipad?` to branch in your test.
922
1073
  # @example The following are the allowed :scroll_position values.
923
1074
  # {:top | :center_vertical | :bottom | :left | :center_horizontal | :right}
924
1075
  #
925
- # @param [Integer] item the index of the item to scroll to
926
- # @param [Integer] section the section of the item to scroll to
927
- # @param [Hash] opts options for controlling the collection view query
1076
+ # @param [Integer] item_index the index of the item to scroll to. Must be >= 0.
1077
+ # @param [Integer] section_index the section of the item to scroll to. Must be > 0.
1078
+ # @param [Hash] options options for controlling the collection view query
928
1079
  # and scroll behavior
929
1080
  #
930
- # @option opts [String] :query ('collectionView')
1081
+ # @option opts [String] :query ("UICollectionView index:0")
931
1082
  # the query that is used to identify which collection view to scroll
932
1083
  #
933
1084
  # @option opts [Symbol] :scroll_position (top)
@@ -936,46 +1087,86 @@ Use `ipad?` to branch in your test.
936
1087
  # @option opts [Boolean] :animate (true)
937
1088
  # should the scrolling be animated
938
1089
  #
939
- # @option opts [String] :failed_message (nil)
1090
+ # @option opts [String] :failure_message (nil)
940
1091
  # a custom error message to display if the scrolling fails - if not
941
1092
  # specified, a generic failure will be displayed
942
1093
  #
943
1094
  # @raise [RuntimeError] if the scroll cannot be performed
944
1095
  # @raise [RuntimeError] :query finds no collection view
945
1096
  # @raise [RuntimeError] the collection view does not contain a cell at item/section
946
- # @raise [RuntimeError] :scroll_position is invalid
947
- def scroll_to_collection_view_item(item, section, opts={})
948
- default_options = {:query => 'collectionView',
1097
+ # @raise [ArgumentError] :scroll_position is invalid
1098
+ # @raise [ArgumentError] item or section is < 0.
1099
+ # @raise [ArgumentError] If the :query value is nil, "", or "*".
1100
+ def scroll_to_collection_view_item(item_index, section_index, options={})
1101
+ default_options = {:query => "UICollectionView index:0",
949
1102
  :scroll_position => :top,
950
1103
  :animate => true,
951
- :failed_message => nil}
952
- opts = default_options.merge(opts)
953
- uiquery = opts[:query]
1104
+ :failure_message => nil}
1105
+ merged_options = default_options.merge(options)
1106
+ uiquery = merged_options[:query]
1107
+
1108
+ if uiquery.nil?
1109
+ raise ArgumentError, "The :query option cannot be nil"
1110
+ end
1111
+
1112
+ if uiquery == ""
1113
+ raise ArgumentError, "The :query option cannot be the empty string"
1114
+ end
1115
+
1116
+ if uiquery == "*"
1117
+ raise ArgumentError, "The :query option cannot be the wildcard '*'"
1118
+ end
1119
+
1120
+ if item_index < 0
1121
+ raise ArgumentError, "Invalid item index: '#{item_index}' - must be >= 0"
1122
+ end
1123
+
1124
+ if section_index < 0
1125
+ raise ArgumentError, "Invalid section index: '#{section_index}' - must be >= 0"
1126
+ end
954
1127
 
955
- scroll_position = opts[:scroll_position]
1128
+ scroll_position = merged_options[:scroll_position]
956
1129
  candidates = [:top, :center_vertical, :bottom, :left, :center_horizontal, :right]
957
- unless candidates.include?(scroll_position)
958
- raise "scroll_position '#{scroll_position}' is not one of '#{candidates}'"
1130
+ if !candidates.include?(scroll_position)
1131
+ raise ArgumentError, %Q[
1132
+
1133
+ Invalid :scroll_position option '#{scroll_position}'. Valid options are:
1134
+
1135
+ #{candidates.join(", ")}
1136
+
1137
+ ]
959
1138
  end
960
1139
 
961
- animate = opts[:animate]
1140
+ animate = merged_options[:animate]
962
1141
 
963
1142
  views_touched = Map.map(uiquery, :collectionViewScroll,
964
- item.to_i, section.to_i,
1143
+ item_index.to_i, section_index.to_i,
965
1144
  scroll_position, animate)
966
1145
 
967
- if opts[:failed_message]
968
- msg = opts[:failed_message]
969
- else
970
- msg = "unable to scroll: '#{uiquery}' to item '#{item}' in section '#{section}'"
1146
+ message = merged_options[:failure_message]
1147
+ if !message
1148
+ message = %Q[
1149
+ Unable to scroll to item index '#{item_index}' in section index '#{section_index}'
1150
+ in CollectionView matched by:
1151
+
1152
+ #{uiquery}
1153
+
1154
+ with options:
1155
+
1156
+ #{merged_options}
1157
+
1158
+ ]
971
1159
  end
972
1160
 
973
- Map.assert_map_results(views_touched, msg)
1161
+ Map.assert_map_results(views_touched, message)
974
1162
  views_touched
975
1163
  end
976
1164
 
977
1165
  # Scrolls to mark in a UICollectionView.
978
1166
  #
1167
+ # Make sure your query matches exactly one UICollectionView. If multiple
1168
+ # views are matched, the results can be unpredictable.
1169
+ #
979
1170
  # @example Scroll to the top of the item with the given mark.
980
1171
  # scroll_to_collection_view_item_with_mark('cat', {:scroll_position => :top})
981
1172
  #
@@ -987,7 +1178,7 @@ Use `ipad?` to branch in your test.
987
1178
  #
988
1179
  # @param [String] mark an accessibility `{label | identifier}` or text in
989
1180
  # or on the item
990
- # @param [Hash] opts options for controlling the collection view query
1181
+ # @param [Hash] options options for controlling the collection view query
991
1182
  # and scroll behavior
992
1183
  #
993
1184
  # @option opts [String] :query ('collectionView')
@@ -996,43 +1187,74 @@ Use `ipad?` to branch in your test.
996
1187
  # the position in the collection view to scroll the item to
997
1188
  # @option opts [Boolean] :animate (true) should the scroll
998
1189
  # be animated
999
- # @option opts [String] :failed_message (nil)
1000
- # a custom error message to display if the scrolling fails - if not
1001
- # specified, a generic failure will be displayed
1190
+ # @option opts [String] :failure_message (nil) a custom error message to
1191
+ # display if the scrolling fails - if not specified, a generic failure
1192
+ # will be displayed
1002
1193
  #
1003
1194
  # @raise [RuntimeError] if the scroll cannot be performed
1004
- # @raise [RuntimeError] if the mark is nil
1005
1195
  # @raise [RuntimeError] :query finds no collection view
1006
1196
  # @raise [RuntimeError] the collection view does not contain a cell
1007
1197
  # with the mark
1008
1198
  # @raise [RuntimeError] :scroll_position is invalid
1009
- def scroll_to_collection_view_item_with_mark(mark, opts={})
1010
- default_options = {:query => 'collectionView',
1199
+ # @raise [ArgumentError] If the :query value is nil, "", or "*".
1200
+ # @raise [ArgumentError] if the mark is nil
1201
+ def scroll_to_collection_view_item_with_mark(mark, options={})
1202
+ default_options = {:query => "UICollectionView index:0",
1011
1203
  :scroll_position => :top,
1012
1204
  :animate => true,
1013
- :failed_message => nil}
1014
- opts = default_options.merge(opts)
1015
- uiquery = opts[:query]
1205
+ :failure_message => nil}
1206
+ merged_options = default_options.merge(options)
1207
+ uiquery = merged_options[:query]
1016
1208
 
1017
1209
  if mark.nil?
1018
- raise 'mark argument cannot be nil'
1210
+ raise ArgumentError, "The mark cannot be nil"
1019
1211
  end
1020
1212
 
1021
- args = []
1022
- scroll_position = opts[:scroll_position]
1213
+ if uiquery.nil?
1214
+ raise ArgumentError, "The :query option cannot be nil"
1215
+ end
1216
+
1217
+ if uiquery == ""
1218
+ raise ArgumentError, "The :query option cannot be the empty string"
1219
+ end
1220
+
1221
+ if uiquery == "*"
1222
+ raise ArgumentError, "The :query option cannot be the wildcard '*'"
1223
+ end
1224
+
1225
+ scroll_position = merged_options[:scroll_position]
1023
1226
  candidates = [:top, :center_vertical, :bottom, :left, :center_horizontal, :right]
1024
- unless candidates.include?(scroll_position)
1025
- raise "scroll_position '#{scroll_position}' is not one of '#{candidates}'"
1227
+ if !candidates.include?(scroll_position)
1228
+ raise ArgumentError, %Q[
1229
+
1230
+ Invalid :scroll_position option '#{scroll_position}'. Valid options are:
1231
+
1232
+ #{candidates.join(", ")}
1233
+
1234
+ ]
1026
1235
  end
1027
1236
 
1028
- args << scroll_position
1029
- args << opts[:animate]
1237
+ args = [scroll_position, merged_options[:animate]]
1030
1238
 
1031
- views_touched = Map.map(uiquery, :collectionViewScrollToItemWithMark,
1239
+ views_touched = Map.map(uiquery,
1240
+ :collectionViewScrollToItemWithMark,
1032
1241
  mark, *args)
1033
1242
 
1034
- msg = opts[:failed_message] || "Unable to scroll: '#{uiquery}' to cell with mark: '#{mark}' with #{opts}"
1035
- Map.assert_map_results(views_touched, msg)
1243
+ message = merged_options[:failure_message]
1244
+ if !message
1245
+ message = %Q[
1246
+ Unable to scroll to item with mark '#{mark}' in UICollectionView matching query:
1247
+
1248
+ #{uiquery}
1249
+
1250
+ with options:
1251
+
1252
+ #{merged_options}
1253
+
1254
+ ]
1255
+ end
1256
+
1257
+ Map.assert_map_results(views_touched, message)
1036
1258
  views_touched
1037
1259
  end
1038
1260
 
@@ -239,6 +239,12 @@ module Calabash
239
239
  ios_version_object.major.to_s
240
240
  end
241
241
 
242
+ # Is this device running iOS 10?
243
+ # @return [Boolean] true if the major version of the OS is 10
244
+ def ios10?
245
+ ios_version_object.major == 10
246
+ end
247
+
242
248
  # Is this device running iOS 9?
243
249
  # @return [Boolean] true if the major version of the OS is 9
244
250
  def ios9?
@@ -24,7 +24,7 @@ module Calabash
24
24
  @world = world
25
25
  end
26
26
 
27
- # @!visibility private
27
+ # Query the UI for elements.
28
28
  #
29
29
  # @example
30
30
  # query({id: "login", :type "Button"})
@@ -126,7 +126,7 @@ module Calabash
126
126
  #
127
127
  # @raise [RuntimeError] if no view matches the uiquery after waiting.
128
128
  def query_for_coordinate(uiquery)
129
- fail_with_screenshot { client.query_for_coordinate(uiquery) }
129
+ with_screenshot_on_failure { client.query_for_coordinate(uiquery) }
130
130
  end
131
131
 
132
132
  # Perform a touch on the center of the first view matched the uiquery.
@@ -140,7 +140,7 @@ module Calabash
140
140
  #
141
141
  # @raise [RuntimeError] if no view matches the uiquery after waiting.
142
142
  def touch(uiquery)
143
- fail_with_screenshot { client.touch(uiquery) }
143
+ with_screenshot_on_failure { client.touch(uiquery) }
144
144
  end
145
145
 
146
146
  # Perform a touch at a coordinate.
@@ -173,7 +173,7 @@ module Calabash
173
173
  #
174
174
  # @raise [RuntimeError] if no view matches the uiquery after waiting.
175
175
  def double_tap(uiquery)
176
- fail_with_screenshot { client.double_tap(uiquery) }
176
+ with_screenshot_on_failure { client.double_tap(uiquery) }
177
177
  end
178
178
 
179
179
  # Perform a two finger tap on the center of the first view matched the uiquery.
@@ -187,7 +187,7 @@ module Calabash
187
187
  #
188
188
  # @raise [RuntimeError] if no view matches the uiquery after waiting.
189
189
  def two_finger_tap(uiquery)
190
- fail_with_screenshot { client.two_finger_tap(uiquery) }
190
+ with_screenshot_on_failure { client.two_finger_tap(uiquery) }
191
191
  end
192
192
 
193
193
  # Perform a long press on the center of the first view matched the uiquery.
@@ -202,7 +202,7 @@ module Calabash
202
202
  #
203
203
  # @raise [RuntimeError] if no view matches the uiquery after waiting.
204
204
  def long_press(uiquery, duration)
205
- fail_with_screenshot { client.long_press(uiquery, {:duration => duration}) }
205
+ with_screenshot_on_failure { client.long_press(uiquery, {:duration => duration}) }
206
206
  end
207
207
 
208
208
  # Returns true if there is a keyboard visible.
@@ -228,7 +228,7 @@ module Calabash
228
228
  # @raise [RuntimeError] if there is no visible keyboard.
229
229
  # @deprecated 0.21.0 Use Core#enter_text
230
230
  def enter_text(text)
231
- fail_with_screenshot { client.enter_text(text) }
231
+ with_screenshot_on_failure { client.enter_text(text) }
232
232
  end
233
233
 
234
234
  # Enter text into the first view matched by uiquery.
@@ -244,7 +244,7 @@ module Calabash
244
244
  #
245
245
  # @deprecated 0.21.0 Use Core#enter_text
246
246
  def enter_text_in(uiquery, text)
247
- fail_with_screenshot do
247
+ with_screenshot_on_failure do
248
248
  client.touch(uiquery)
249
249
  client.wait_for_keyboard
250
250
  client.enter_text(text)
@@ -311,20 +311,6 @@ module Calabash
311
311
  client.springboard_alert
312
312
  end
313
313
 
314
- =begin
315
- PROTECTED
316
- =end
317
- protected
318
-
319
- # @!visibility private
320
- def method_missing(name, *args, &block)
321
- if world.respond_to?(name)
322
- world.send(name, *args, &block)
323
- else
324
- super
325
- end
326
- end
327
-
328
314
  =begin
329
315
  PRIVATE
330
316
  =end
@@ -334,7 +320,7 @@ PRIVATE
334
320
  attr_reader :client, :world
335
321
 
336
322
  # @!visibility private
337
- def fail_with_screenshot(&block)
323
+ def with_screenshot_on_failure(&block)
338
324
  begin
339
325
  block.call
340
326
  rescue => e
@@ -192,6 +192,14 @@ module Calabash
192
192
  _default_device_or_create.ios9?
193
193
  end
194
194
 
195
+ # Is the device under test running iOS 10?
196
+ #
197
+ # @raise [RuntimeError] if the server cannot be reached
198
+ # @return [Boolean] true if device under test is running iOS 9
199
+ def ios10?
200
+ _default_device_or_create.ios10?
201
+ end
202
+
195
203
  # Is the app that is being tested an iPhone app emulated on an iPad?
196
204
  #
197
205
  # @see Calabash::Cucumber::IPad
@@ -173,14 +173,19 @@ module Calabash
173
173
  screenshot_and_raise "There must be a visible keyboard"
174
174
  end
175
175
 
176
- ['textField', 'textView'].each do |ui_class|
177
- query = "#{ui_class} isFirstResponder:1"
178
- result = _query_wrapper(query, :text)
179
- if !result.empty?
180
- return result.first
181
- end
182
- end
183
- ""
176
+ query = "* isFirstResponder:1"
177
+ elements = _query_wrapper(query, :text)
178
+
179
+ return "" if elements.count == 0
180
+
181
+ text = elements[0]
182
+
183
+ # first responder did not respond to :text selector
184
+ return "" if text == "*****"
185
+
186
+ return "" if text.nil?
187
+
188
+ text
184
189
  end
185
190
 
186
191
  # @visibility private
@@ -132,11 +132,11 @@ module Calabash
132
132
  #
133
133
  # +1 for tools to ask physical devices about attributes.
134
134
  def device
135
- @device ||= lambda do
135
+ @device ||= begin
136
136
  _, body = Calabash::Cucumber::HTTP.ensure_connectivity
137
137
  endpoint = Calabash::Cucumber::Environment.device_endpoint
138
138
  Calabash::Cucumber::Device.new(endpoint, body)
139
- end.call
139
+ end
140
140
  end
141
141
 
142
142
  # @!visibility private
@@ -170,20 +170,6 @@ module Calabash
170
170
  :http_connection_timeout => 10}
171
171
  merged_options = default_options.merge(options)
172
172
 
173
- @run_loop = RunLoop::HostCache.default.read
174
-
175
- if @run_loop[:automator] == :device_agent
176
- # TODO Attach to DeviceAgent - run-loop supports this!
177
- # TODO Rewrite UIA methods to raise in the context of UIA
178
- raise RuntimeError, %Q[
179
-
180
- Cannot attach to DeviceAgent automator.
181
-
182
- This behavior is not implemented yet.
183
-
184
- ]
185
- end
186
-
187
173
  begin
188
174
  Calabash::Cucumber::HTTP.ensure_connectivity(merged_options)
189
175
  rescue Calabash::Cucumber::ServerNotRespondingError => _
@@ -207,13 +193,22 @@ Try `start_test_server_in_background`
207
193
  return false
208
194
  end
209
195
 
210
- if run_loop[:pid]
211
- @automator = Calabash::Cucumber::Automator::Instruments.new(run_loop)
196
+ # TODO check that the :pid is alive - no sense attaching if Automator
197
+ # is not running.
198
+ run_loop_cache = RunLoop::HostCache.default.read
199
+
200
+ if run_loop_cache[:automator] == :device_agent
201
+ # Sets the @run_loop variable to a new RunLoop::DeviceAgent::Client
202
+ # instance.
203
+ @automator = _attach_to_device_agent!(run_loop_cache)
204
+ elsif run_loop_cache[:automator] == :instruments
205
+ @run_loop = run_loop_cache
206
+ @automator = Calabash::Cucumber::Automator::Instruments.new(run_loop_cache)
212
207
  else
213
208
  RunLoop.log_warn(
214
209
  %Q[
215
210
 
216
- Connected to an app that was not launched by Calabash using instruments.
211
+ Connected to an app that was not launched by Calabash using instruments or DeviceAgent.
217
212
 
218
213
  Queries will work, but gestures and other automator actions will not.
219
214
 
@@ -627,6 +622,28 @@ true. Please remove this method call from your hooks.
627
622
  value
628
623
  end
629
624
  end
625
+
626
+ # @!visibility private
627
+ def _attach_to_device_agent!(hash)
628
+ simctl = Calabash::Cucumber::Environment.simctl
629
+ instruments = Calabash::Cucumber::Environment.instruments
630
+ xcode = Calabash::Cucumber::Environment.xcode
631
+
632
+ options = { simctl: simctl, instruments: instruments, xcode: xcode}
633
+ device = RunLoop::Device.device_with_identifier(hash[:udid], options)
634
+ bundle_id = hash[:app]
635
+
636
+ options = { cbx_launcher: hash[:launcher] }
637
+ cbx_launcher = RunLoop::DeviceAgent::Client.detect_cbx_launcher(options, device)
638
+ launcher_options = hash[:launcher_options]
639
+
640
+ device_agent_client = RunLoop::DeviceAgent::Client.new(bundle_id,
641
+ device,
642
+ cbx_launcher,
643
+ launcher_options)
644
+ @run_loop = device_agent_client
645
+ Calabash::Cucumber::Automator::DeviceAgent.new(@run_loop)
646
+ end
630
647
  end
631
648
  end
632
649
  end
@@ -145,7 +145,7 @@ details: #{hash["details"]}
145
145
  def self.assert_map_results(map_results, msg)
146
146
  compact = map_results.compact
147
147
  if compact.empty? or compact.member? '<VOID>' or compact.member? '*****'
148
- Map.new.screenshot_and_raise msg
148
+ self.map_factory.screenshot_and_raise msg
149
149
  end
150
150
  end
151
151
 
@@ -163,7 +163,7 @@ module Calabash
163
163
  hash = {
164
164
  :event_name => "session",
165
165
  :data_version => DATA_VERSION,
166
- :user_id => user_id
166
+ :distinct_id => user_id
167
167
  }
168
168
 
169
169
  if allowed == "system_info"
@@ -178,6 +178,9 @@ module Calabash
178
178
  :used_cucumber => used_cucumber?,
179
179
 
180
180
  :version => Calabash::Cucumber::VERSION,
181
+ :run_loop_version => RunLoop::VERSION,
182
+
183
+ :xcode_version => RunLoop::Xcode.new.version,
181
184
 
182
185
  :ci => RunLoop::Environment.ci?,
183
186
  :jenkins => RunLoop::Environment.jenkins?,
@@ -3,10 +3,10 @@ module Calabash
3
3
 
4
4
  # @!visibility public
5
5
  # The Calabash iOS gem version.
6
- VERSION = "0.20.0"
6
+ VERSION = "0.20.3"
7
7
 
8
8
  # @!visibility public
9
9
  # The minimum required version of the Calabash embedded server.
10
- MIN_SERVER_VERSION = "0.20.0"
10
+ MIN_SERVER_VERSION = "0.20.3"
11
11
  end
12
12
  end
@@ -89,3 +89,4 @@ Calabash::Cucumber::UIA.redefine_instance_methods_if_necessary(xcode)
89
89
 
90
90
  puts_console_details
91
91
  puts_message_of_the_day
92
+ _try_to_attach
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: calabash-cucumber
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.20.0
4
+ version: 0.20.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karl Krukow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-13 00:00:00.000000000 Z
11
+ date: 2016-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cucumber
@@ -84,7 +84,7 @@ dependencies:
84
84
  requirements:
85
85
  - - ">="
86
86
  - !ruby/object:Gem::Version
87
- version: 2.3.2
87
+ version: 2.7.1
88
88
  - - "<"
89
89
  - !ruby/object:Gem::Version
90
90
  version: '3.0'
@@ -94,7 +94,7 @@ dependencies:
94
94
  requirements:
95
95
  - - ">="
96
96
  - !ruby/object:Gem::Version
97
- version: 2.3.2
97
+ version: 2.7.1
98
98
  - - "<"
99
99
  - !ruby/object:Gem::Version
100
100
  version: '3.0'
@@ -132,7 +132,7 @@ dependencies:
132
132
  requirements:
133
133
  - - ">="
134
134
  - !ruby/object:Gem::Version
135
- version: 2.2.0
135
+ version: 2.2.2
136
136
  - - "<"
137
137
  - !ruby/object:Gem::Version
138
138
  version: '3.0'
@@ -142,7 +142,7 @@ dependencies:
142
142
  requirements:
143
143
  - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: 2.2.0
145
+ version: 2.2.2
146
146
  - - "<"
147
147
  - !ruby/object:Gem::Version
148
148
  version: '3.0'
@@ -286,6 +286,20 @@ dependencies:
286
286
  - - ">="
287
287
  - !ruby/object:Gem::Version
288
288
  version: '0'
289
+ - !ruby/object:Gem::Dependency
290
+ name: rb-readline
291
+ requirement: !ruby/object:Gem::Requirement
292
+ requirements:
293
+ - - ">="
294
+ - !ruby/object:Gem::Version
295
+ version: '0'
296
+ type: :development
297
+ prerelease: false
298
+ version_requirements: !ruby/object:Gem::Requirement
299
+ requirements:
300
+ - - ">="
301
+ - !ruby/object:Gem::Version
302
+ version: '0'
289
303
  - !ruby/object:Gem::Dependency
290
304
  name: guard-rspec
291
305
  requirement: !ruby/object:Gem::Requirement