calabash 2.0.0.pre10 → 2.0.0.pre11

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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -17
  3. data/bin/calabash +3 -4
  4. data/lib/calabash.rb +53 -10
  5. data/lib/calabash/android.rb +89 -28
  6. data/lib/calabash/android/adb.rb +32 -20
  7. data/lib/calabash/android/application.rb +1 -1
  8. data/lib/calabash/android/build/builder.rb +1 -1
  9. data/lib/calabash/android/build/java_keystore.rb +1 -1
  10. data/lib/calabash/android/build/resigner.rb +1 -1
  11. data/lib/calabash/android/device.rb +22 -66
  12. data/lib/calabash/android/device/helper_application.rb +95 -0
  13. data/lib/calabash/android/environment.rb +14 -1
  14. data/lib/calabash/android/gestures.rb +6 -22
  15. data/lib/calabash/android/interactions.rb +14 -17
  16. data/lib/calabash/android/lib/.irbrc +9 -1
  17. data/lib/calabash/android/lib/AndroidManifest.xml +23 -2
  18. data/lib/calabash/android/lib/HelperApplication.apk +0 -0
  19. data/lib/calabash/android/lib/HelperApplicationTestServer.apk +0 -0
  20. data/lib/calabash/android/lib/TestServer.apk +0 -0
  21. data/lib/calabash/android/life_cycle.rb +3 -3
  22. data/lib/calabash/android/orientation.rb +8 -8
  23. data/lib/calabash/android/physical_buttons.rb +19 -16
  24. data/lib/calabash/android/server.rb +1 -1
  25. data/lib/calabash/android/text.rb +12 -12
  26. data/lib/calabash/android/web.rb +12 -0
  27. data/lib/calabash/application.rb +3 -0
  28. data/lib/calabash/cli/generate.rb +8 -18
  29. data/lib/calabash/cli/helpers.rb +4 -9
  30. data/lib/calabash/cli/run.rb +1 -1
  31. data/lib/calabash/console_helpers.rb +179 -11
  32. data/lib/calabash/device.rb +4 -19
  33. data/lib/calabash/gestures.rb +292 -198
  34. data/lib/calabash/interactions.rb +3 -40
  35. data/lib/calabash/internal.rb +48 -0
  36. data/lib/calabash/ios.rb +76 -16
  37. data/lib/calabash/ios/automator.rb +9 -0
  38. data/lib/calabash/ios/automator/automator.rb +217 -0
  39. data/lib/calabash/ios/automator/coordinates.rb +37 -0
  40. data/lib/calabash/ios/automator/device_agent.rb +379 -0
  41. data/lib/calabash/ios/conditions.rb +1 -1
  42. data/lib/calabash/ios/console_helpers.rb +2 -2
  43. data/lib/calabash/ios/date_picker.rb +10 -8
  44. data/lib/calabash/ios/device.rb +0 -1
  45. data/lib/calabash/ios/device/device_implementation.rb +9 -21
  46. data/lib/calabash/ios/device/gestures_mixin.rb +53 -55
  47. data/lib/calabash/ios/device/keyboard_mixin.rb +21 -0
  48. data/lib/calabash/ios/device/rotation_mixin.rb +3 -65
  49. data/lib/calabash/ios/gestures.rb +24 -90
  50. data/lib/calabash/ios/interactions.rb +1 -6
  51. data/lib/calabash/ios/lib/.irbrc +9 -2
  52. data/lib/calabash/ios/orientation.rb +8 -8
  53. data/lib/calabash/ios/runtime.rb +14 -14
  54. data/lib/calabash/ios/scroll.rb +25 -17
  55. data/lib/calabash/ios/slider.rb +11 -18
  56. data/lib/calabash/ios/text.rb +20 -74
  57. data/lib/calabash/ios/uia.rb +1 -1
  58. data/lib/calabash/ios/web.rb +10 -0
  59. data/lib/calabash/lib/skeleton/{Gemfile → Gemfile.skeleton} +0 -0
  60. data/lib/calabash/lib/skeleton/config/{cucumber.yml → cucumber.yml.skeleton} +0 -0
  61. data/lib/calabash/lib/skeleton/features/{sample.feature → sample.feature.skeleton} +0 -0
  62. data/lib/calabash/lib/skeleton/features/step_definitions/{calabash_steps.rb → sample_steps.rb.skeleton} +8 -8
  63. data/lib/calabash/lib/skeleton/features/support/{dry_run.rb → dry_run.rb.skeleton} +2 -5
  64. data/lib/calabash/lib/skeleton/features/support/{env.rb → env.rb.skeleton} +2 -8
  65. data/lib/calabash/lib/skeleton/features/support/hooks.rb.skeleton +34 -0
  66. data/lib/calabash/life_cycle.rb +16 -8
  67. data/lib/calabash/location.rb +14 -15
  68. data/lib/calabash/orientation.rb +8 -8
  69. data/lib/calabash/page.rb +1 -4
  70. data/lib/calabash/retry.rb +33 -0
  71. data/lib/calabash/screenshot.rb +3 -3
  72. data/lib/calabash/stubs.rb +21 -0
  73. data/lib/calabash/text.rb +31 -19
  74. data/lib/calabash/utility.rb +41 -8
  75. data/lib/calabash/version.rb +1 -1
  76. data/lib/calabash/wait.rb +177 -192
  77. data/lib/calabash/web.rb +44 -0
  78. metadata +39 -32
  79. data/lib/calabash/ios/device/text_mixin.rb +0 -21
  80. data/lib/calabash/lib/skeleton/features/support/hooks.rb +0 -83
@@ -0,0 +1,37 @@
1
+ module Calabash
2
+ module IOS
3
+ # @!visibility private
4
+ module Automator
5
+ # @!visibility private
6
+ class Coordinates
7
+ # @!visibility private
8
+ def self.end_point_for_swipe(dir, element)
9
+ case dir
10
+ when :left
11
+ degrees = 0
12
+ when :up
13
+ degrees = 90
14
+ when :right
15
+ degrees = 180
16
+ when :down
17
+ degrees = 270
18
+ end
19
+ radians = degrees * Math::PI / 180.0
20
+
21
+ element_width = element["rect"]["width"]
22
+ element_height = element["rect"]["height"]
23
+ x_center = element["rect"]["center_x"]
24
+ y_center = element["rect"]["center_y"]
25
+ radius = ([element_width, element_height].min) * 0.33
26
+ to_x = x_center - (radius * Math.cos(radians))
27
+ to_y = y_center - (radius * Math.sin(radians))
28
+ {:x => to_x, :y => to_y}
29
+ end
30
+
31
+ def self.distance(from, to)
32
+ Math.sqrt((from[:x] - to[:x]) ** 2 + (from[:y] - to[:y]) ** 2)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,379 @@
1
+ require "run_loop"
2
+
3
+ module Calabash
4
+ module IOS
5
+ # @!visibility private
6
+ module Automator
7
+ # @!visibility private
8
+ class DeviceAgent < Calabash::IOS::Automator::Automator
9
+
10
+ # @!visibility private
11
+ def self.expect_valid_args(args)
12
+ if args.nil?
13
+ raise ArgumentError, "Expected args to be a non-nil Array"
14
+ end
15
+
16
+ if !args.is_a?(Array)
17
+ raise ArgumentError, %Q[Expected args to be an Array, found:
18
+
19
+ args = #{args}
20
+
21
+ ]
22
+ end
23
+
24
+ if args.count != 1
25
+ raise(ArgumentError,
26
+ %Q[Expected args to be an Array with one element, found:
27
+
28
+ args = #{args}
29
+
30
+ ])
31
+ end
32
+
33
+ if !args[0].is_a?(RunLoop::DeviceAgent::Client)
34
+ raise(ArgumentError, %Q[
35
+ Expected first element of args to be a RunLoop::DeviceAgent::Client instance, found:
36
+ args[0] = #{args[0]}])
37
+ end
38
+
39
+ true
40
+ end
41
+
42
+ attr_reader :client
43
+
44
+ # @!visibility private
45
+ def initialize(*args)
46
+ DeviceAgent.expect_valid_args(args)
47
+ @client = args[0]
48
+ end
49
+
50
+ # @!visibility private
51
+ def name
52
+ :device_agent
53
+ end
54
+
55
+ # @!visibility private
56
+ def stop
57
+ client.send(:shutdown)
58
+ end
59
+
60
+ # @!visibility private
61
+ def running?
62
+ client.send(:running?)
63
+ end
64
+
65
+ # @!visibility private
66
+ def session_delete
67
+ client.send(:session_delete)
68
+ end
69
+
70
+ # @!visibility private
71
+ def touch(options)
72
+ client.perform_coordinate_gesture("touch",
73
+ options[:coordinates][:x],
74
+ options[:coordinates][:y])
75
+ end
76
+
77
+ # @!visibility private
78
+ def double_tap(options)
79
+ client.perform_coordinate_gesture("double_tap",
80
+ options[:coordinates][:x],
81
+ options[:coordinates][:y])
82
+ end
83
+
84
+ # @!visibility private
85
+ def two_finger_tap(options)
86
+ client.perform_coordinate_gesture("two_finger_tap",
87
+ options[:coordinates][:x],
88
+ options[:coordinates][:y])
89
+ end
90
+
91
+ # @!visibility private
92
+ def touch_hold(options)
93
+ client.perform_coordinate_gesture("touch",
94
+ options[:coordinates][:x],
95
+ options[:coordinates][:y],
96
+ {:duration => options[:duration]})
97
+ end
98
+
99
+ # @!visibility private
100
+ def pinch(in_out, options)
101
+ dupped_options = options.dup
102
+
103
+ if dupped_options[:query].nil?
104
+ element = element_for_device_screen
105
+ coordinates = point_from(element, options)
106
+ else
107
+ hash = query_for_coordinates(dupped_options)
108
+ element = hash[:view]
109
+ coordinates = hash[:coordinates]
110
+ end
111
+
112
+ in_out = in_out.to_s
113
+ duration = dupped_options[:duration]
114
+ amount = dupped_options[:amount]
115
+
116
+ gesture_options = {
117
+ :pinch_direction => in_out,
118
+ :amount => amount,
119
+ :duration => duration
120
+ }
121
+
122
+ client.perform_coordinate_gesture("pinch",
123
+ coordinates[:x],
124
+ coordinates[:y],
125
+ gesture_options)
126
+
127
+ [element]
128
+ end
129
+
130
+ MIN_SECONDS_PR_PIXEL_FOR_PAN = 0.006
131
+
132
+ # @!visibility private
133
+ def pan(options)
134
+ # A pan seems to require 0.005 seconds pr. pixel.
135
+ # Therefore we require that the duration is at least
136
+ # 0.005 seconds pr pixel.
137
+
138
+ duration = [
139
+ options[:duration],
140
+ MIN_SECONDS_PR_PIXEL_FOR_PAN *
141
+ Coordinates.distance(options[:coordinates][:from], options[:coordinates][:to])
142
+ ].max
143
+
144
+ client.pan_between_coordinates(options[:coordinates][:from],
145
+ options[:coordinates][:to],
146
+ {:duration => duration})
147
+ end
148
+
149
+ # @!visibility private
150
+ def flick(options)
151
+ gesture_options = {
152
+ duration: 0.2
153
+ }
154
+
155
+ delta = options[:delta]
156
+
157
+ # The UIA deltas are too small.
158
+ scaled_delta = {
159
+ :x => delta[:x] * 2.0,
160
+ :y => delta[:y] * 2.0
161
+ }
162
+
163
+ hash = query_for_coordinates(options)
164
+ view = hash[:view]
165
+
166
+ start_point = point_from(view)
167
+ end_point = point_from(view, {:offset => scaled_delta})
168
+
169
+ client.pan_between_coordinates(start_point,
170
+ end_point,
171
+ gesture_options)
172
+ [view]
173
+ end
174
+
175
+ # @!visibility private
176
+ def enter_text_with_keyboard(string, options={})
177
+ client.enter_text(string)
178
+ end
179
+
180
+ # @!visibility private
181
+ def enter_char_with_keyboard(char)
182
+ client.enter_text(char)
183
+ end
184
+
185
+ # @!visibility private
186
+ def char_for_keyboard_action(action_key)
187
+ SPECIAL_ACTION_CHARS[action_key]
188
+ end
189
+
190
+ # @!visibility private
191
+ def tap_keyboard_action_key(return_key_type_of_first_responder)
192
+ mark = mark_for_return_key_of_first_responder(return_key_type_of_first_responder)
193
+ if mark
194
+ begin
195
+ # The underlying query for coordinates always expects results.
196
+ value = client.touch({marked: mark})
197
+ return value
198
+ rescue RuntimeError => _
199
+ RunLoop.log_debug("Cannot find mark '#{mark}' with query; will send a newline")
200
+ end
201
+ else
202
+ RunLoop.log_debug("Cannot find keyboard return key type; sending a newline")
203
+ end
204
+
205
+ code = char_for_keyboard_action("Return")
206
+ client.enter_text(code)
207
+ end
208
+
209
+ # @!visibility private
210
+ def tap_keyboard_delete_key
211
+ client.touch({marked: "delete"})
212
+ end
213
+
214
+ # @!visibility private
215
+ def fast_enter_text(text)
216
+ client.enter_text(text)
217
+ end
218
+
219
+ # @!visibility private
220
+ #
221
+ # Stable across different keyboard languages.
222
+ def dismiss_ipad_keyboard
223
+ client.touch({marked: "Hide keyboard"})
224
+ end
225
+
226
+ # @!visibility private
227
+ def rotate(direction, status_bar_orientation)
228
+ # Caller is responsible for normalizing and verifying direction.
229
+ current_orientation = status_bar_orientation.to_sym
230
+ key = Automator.orientation_key(direction, current_orientation)
231
+ position = Automator.orientation_for_key(key)
232
+ rotate_home_button_to(position, status_bar_orientation)
233
+ end
234
+
235
+ # @!visibility private
236
+ def rotate_home_button_to(position, status_bar_orientation)
237
+ # Caller is responsible for normalizing and verifying position.
238
+ client.rotate_home_button_to(position)
239
+ status_bar_orientation.to_sym
240
+ end
241
+
242
+ private
243
+
244
+ # @!visibility private
245
+ #
246
+ # Calls #point_from which applies any :offset supplied in the options.
247
+ def query_for_coordinates(options)
248
+ uiquery = options[:query]
249
+
250
+ if uiquery.nil?
251
+ offset = options[:offset]
252
+
253
+ if offset && offset[:x] && offset[:y]
254
+ {
255
+ :coordinates => offset,
256
+ :view => offset
257
+ }
258
+ else
259
+ raise ArgumentError, %Q[
260
+ If query is nil, there must be a valid offset in the options.
261
+
262
+ Expected: options[:offset] = {:x => NUMERIC, :y => NUMERIC}
263
+ Actual: options[:offset] = #{offset ? offset : "nil"}
264
+
265
+ ]
266
+ end
267
+ else
268
+
269
+ first_element = first_element_for_query(uiquery)
270
+
271
+ if first_element.nil?
272
+ msg = %Q[
273
+ Could not find any views with query:
274
+
275
+ #{uiquery}
276
+
277
+ Make sure your query returns at least one view.
278
+
279
+ ]
280
+ Calabash::Cucumber::Map.new.screenshot_and_raise(msg)
281
+ else
282
+ {
283
+ :coordinates => point_from(first_element, options),
284
+ :view => first_element
285
+ }
286
+ end
287
+ end
288
+ end
289
+
290
+ # @!visibility private
291
+ def first_element_for_query(uiquery)
292
+
293
+ if uiquery.nil?
294
+ raise ArgumentError, "Query cannot be nil"
295
+ end
296
+
297
+ # Will raise if response "outcome" is not SUCCESS
298
+ results = Calabash::Cucumber::Map.raw_map(uiquery, :query)["results"]
299
+
300
+ if results.empty?
301
+ nil
302
+ else
303
+ results[0]
304
+ end
305
+ end
306
+
307
+ # @!visibility private
308
+ def element_for_device_screen
309
+ screen_dimensions = device.screen_dimensions
310
+
311
+ scale = screen_dimensions[:scale]
312
+ height = (screen_dimensions[:height]/scale).to_i
313
+ center_y = (height/2)
314
+ width = (screen_dimensions[:width]/scale).to_i
315
+ center_x = (width/2)
316
+
317
+ {
318
+ "screen" => true,
319
+ "rect" => {
320
+ "height" => height,
321
+ "width" => width,
322
+ "center_x" => center_x,
323
+ "center_y" => center_y
324
+ }
325
+ }
326
+ end
327
+
328
+ # @!visibility private
329
+ #
330
+ # Don't change the double quotes.
331
+ SPECIAL_ACTION_CHARS = {
332
+ "Delete" => "\b",
333
+ "Return" => "\n"
334
+ }.freeze
335
+
336
+ # @!visibility private
337
+ #
338
+ # Keys are from the UIReturnKeyType enum.
339
+ #
340
+ # The values are localization independent identifiers - these are
341
+ # stable across localizations and keyboard languages. The exception is
342
+ # Continue which is not stable.
343
+ RETURN_KEY_TYPE = {
344
+ 0 => "Return",
345
+ 1 => "Go",
346
+ 2 => "Google",
347
+ # Needs special physical device vs simulator handling.
348
+ 3 => "Join",
349
+ 4 => "Next",
350
+ 5 => "Route",
351
+ 6 => "Search",
352
+ 7 => "Send",
353
+ 8 => "Yahoo",
354
+ 9 => "Done",
355
+ 10 => "Emergency call",
356
+ # https://xamarin.atlassian.net/browse/TCFW-344
357
+ # Localized!!! Apple bug.
358
+ 11 => "Continue"
359
+ }.freeze
360
+
361
+ # @!visibility private
362
+ def mark_for_return_key_type(number)
363
+ # https://xamarin.atlassian.net/browse/TCFW-361
364
+ value = RETURN_KEY_TYPE[number]
365
+ if value == "Join" && !simulator?
366
+ "Join:"
367
+ else
368
+ value
369
+ end
370
+ end
371
+
372
+ # @!visibility private
373
+ def mark_for_return_key_of_first_responder(return_key_type_of_first_responder)
374
+ mark_for_return_key_type(return_key_type_of_first_responder)
375
+ end
376
+ end
377
+ end
378
+ end
379
+ end
@@ -72,7 +72,7 @@ module Calabash
72
72
  # @return [nil] When the condition is satisfied.
73
73
  # @raise [Calabash::Wait::TimeoutError] When the timeout is exceeded.
74
74
  def wait_for_condition(condition, timeout, timeout_message, query='*')
75
- unless Device.default.condition_route(condition, timeout, query)
75
+ unless Calabash::Internal.with_default_device(required_os: :ios) {|device| device.condition_route(condition, timeout, query)}
76
76
  raise Calabash::Wait::TimeoutError, timeout_message
77
77
  end
78
78
  true
@@ -55,7 +55,7 @@ module Calabash
55
55
  Calabash::Device.default = device
56
56
 
57
57
  begin
58
- Calabash::Device.default.ensure_test_server_ready({:timeout => 4})
58
+ Calabash::Internal.with_default_device(required_os: :ios) {|device| device.ensure_test_server_ready({:timeout => 4})}
59
59
  rescue RuntimeError => e
60
60
  if e.to_s == 'Calabash server did not respond'
61
61
  raise RuntimeError, 'You can only attach to a running Calabash iOS App'
@@ -65,7 +65,7 @@ module Calabash
65
65
  end
66
66
 
67
67
  run_loop_device = device.send(:run_loop_device)
68
- result = Calabash::Device.default.send(:attach_to_run_loop, run_loop_device, uia_strategy)
68
+ result = Calabash::Internal.with_default_device(required_os: :ios) {|device| device.send(:attach_to_run_loop, run_loop_device, uia_strategy)}
69
69
  result[:application] = Calabash::Application.default
70
70
  result
71
71
  end
@@ -378,14 +378,16 @@ module Calabash
378
378
  objc_format = date_picker_objc_date_format
379
379
  target_date_string = date_time.strftime(ruby_format).squeeze(' ').strip
380
380
 
381
- Device.default.map_route(query,
382
- :changeDatePickerDate,
383
- target_date_string,
384
- objc_format,
385
- # notify targets
386
- true,
387
- # animate
388
- true)
381
+ Calabash::Internal.with_default_device(required_os: :ios) do |device|
382
+ device.map_route(query,
383
+ :changeDatePickerDate,
384
+ target_date_string,
385
+ objc_format,
386
+ # notify targets
387
+ true,
388
+ # animate
389
+ true)
390
+ end
389
391
  end
390
392
 
391
393
  private