calabash 2.0.0.pre10 → 2.0.0.pre11

Sign up to get free protection for your applications and to get access to all the features.
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