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.
- checksums.yaml +4 -4
- data/README.md +5 -17
- data/bin/calabash +3 -4
- data/lib/calabash.rb +53 -10
- data/lib/calabash/android.rb +89 -28
- data/lib/calabash/android/adb.rb +32 -20
- data/lib/calabash/android/application.rb +1 -1
- data/lib/calabash/android/build/builder.rb +1 -1
- data/lib/calabash/android/build/java_keystore.rb +1 -1
- data/lib/calabash/android/build/resigner.rb +1 -1
- data/lib/calabash/android/device.rb +22 -66
- data/lib/calabash/android/device/helper_application.rb +95 -0
- data/lib/calabash/android/environment.rb +14 -1
- data/lib/calabash/android/gestures.rb +6 -22
- data/lib/calabash/android/interactions.rb +14 -17
- data/lib/calabash/android/lib/.irbrc +9 -1
- data/lib/calabash/android/lib/AndroidManifest.xml +23 -2
- data/lib/calabash/android/lib/HelperApplication.apk +0 -0
- data/lib/calabash/android/lib/HelperApplicationTestServer.apk +0 -0
- data/lib/calabash/android/lib/TestServer.apk +0 -0
- data/lib/calabash/android/life_cycle.rb +3 -3
- data/lib/calabash/android/orientation.rb +8 -8
- data/lib/calabash/android/physical_buttons.rb +19 -16
- data/lib/calabash/android/server.rb +1 -1
- data/lib/calabash/android/text.rb +12 -12
- data/lib/calabash/android/web.rb +12 -0
- data/lib/calabash/application.rb +3 -0
- data/lib/calabash/cli/generate.rb +8 -18
- data/lib/calabash/cli/helpers.rb +4 -9
- data/lib/calabash/cli/run.rb +1 -1
- data/lib/calabash/console_helpers.rb +179 -11
- data/lib/calabash/device.rb +4 -19
- data/lib/calabash/gestures.rb +292 -198
- data/lib/calabash/interactions.rb +3 -40
- data/lib/calabash/internal.rb +48 -0
- data/lib/calabash/ios.rb +76 -16
- data/lib/calabash/ios/automator.rb +9 -0
- data/lib/calabash/ios/automator/automator.rb +217 -0
- data/lib/calabash/ios/automator/coordinates.rb +37 -0
- data/lib/calabash/ios/automator/device_agent.rb +379 -0
- data/lib/calabash/ios/conditions.rb +1 -1
- data/lib/calabash/ios/console_helpers.rb +2 -2
- data/lib/calabash/ios/date_picker.rb +10 -8
- data/lib/calabash/ios/device.rb +0 -1
- data/lib/calabash/ios/device/device_implementation.rb +9 -21
- data/lib/calabash/ios/device/gestures_mixin.rb +53 -55
- data/lib/calabash/ios/device/keyboard_mixin.rb +21 -0
- data/lib/calabash/ios/device/rotation_mixin.rb +3 -65
- data/lib/calabash/ios/gestures.rb +24 -90
- data/lib/calabash/ios/interactions.rb +1 -6
- data/lib/calabash/ios/lib/.irbrc +9 -2
- data/lib/calabash/ios/orientation.rb +8 -8
- data/lib/calabash/ios/runtime.rb +14 -14
- data/lib/calabash/ios/scroll.rb +25 -17
- data/lib/calabash/ios/slider.rb +11 -18
- data/lib/calabash/ios/text.rb +20 -74
- data/lib/calabash/ios/uia.rb +1 -1
- data/lib/calabash/ios/web.rb +10 -0
- data/lib/calabash/lib/skeleton/{Gemfile → Gemfile.skeleton} +0 -0
- data/lib/calabash/lib/skeleton/config/{cucumber.yml → cucumber.yml.skeleton} +0 -0
- data/lib/calabash/lib/skeleton/features/{sample.feature → sample.feature.skeleton} +0 -0
- data/lib/calabash/lib/skeleton/features/step_definitions/{calabash_steps.rb → sample_steps.rb.skeleton} +8 -8
- data/lib/calabash/lib/skeleton/features/support/{dry_run.rb → dry_run.rb.skeleton} +2 -5
- data/lib/calabash/lib/skeleton/features/support/{env.rb → env.rb.skeleton} +2 -8
- data/lib/calabash/lib/skeleton/features/support/hooks.rb.skeleton +34 -0
- data/lib/calabash/life_cycle.rb +16 -8
- data/lib/calabash/location.rb +14 -15
- data/lib/calabash/orientation.rb +8 -8
- data/lib/calabash/page.rb +1 -4
- data/lib/calabash/retry.rb +33 -0
- data/lib/calabash/screenshot.rb +3 -3
- data/lib/calabash/stubs.rb +21 -0
- data/lib/calabash/text.rb +31 -19
- data/lib/calabash/utility.rb +41 -8
- data/lib/calabash/version.rb +1 -1
- data/lib/calabash/wait.rb +177 -192
- data/lib/calabash/web.rb +44 -0
- metadata +39 -32
- data/lib/calabash/ios/device/text_mixin.rb +0 -21
- 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
|
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::
|
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::
|
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
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|