calabash 2.0.0.pre2 → 2.0.0.pre3
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 +1 -26
- data/lib/calabash/android/adb.rb +9 -0
- data/lib/calabash/android/build/builder.rb +2 -0
- data/lib/calabash/android/build/java_keystore.rb +10 -0
- data/lib/calabash/android/build/resigner.rb +4 -0
- data/lib/calabash/android/build/test_server.rb +2 -0
- data/lib/calabash/android/defaults.rb +1 -0
- data/lib/calabash/android/device.rb +18 -0
- data/lib/calabash/android/interactions.rb +2 -0
- data/lib/calabash/android/lib/TestServer.apk +0 -0
- data/lib/calabash/android/life_cycle.rb +1 -0
- data/lib/calabash/android/physical_buttons.rb +8 -0
- data/lib/calabash/android/screenshot.rb +1 -0
- data/lib/calabash/android/server.rb +1 -0
- data/lib/calabash/android/text.rb +1 -0
- data/lib/calabash/cli/build.rb +1 -0
- data/lib/calabash/cli/console.rb +2 -0
- data/lib/calabash/cli/generate.rb +3 -0
- data/lib/calabash/cli/helpers.rb +6 -0
- data/lib/calabash/cli/resign.rb +1 -0
- data/lib/calabash/cli/run.rb +2 -0
- data/lib/calabash/cli/setup_keystore.rb +2 -0
- data/lib/calabash/color.rb +7 -0
- data/lib/calabash/defaults.rb +1 -0
- data/lib/calabash/device.rb +2 -0
- data/lib/calabash/environment.rb +1 -0
- data/lib/calabash/http/retriable_client.rb +2 -0
- data/lib/calabash/interactions.rb +1 -0
- data/lib/calabash/ios/conditions.rb +3 -0
- data/lib/calabash/ios/date_picker.rb +412 -0
- data/lib/calabash/ios/defaults.rb +1 -0
- data/lib/calabash/ios/device/device_implementation.rb +5 -1
- data/lib/calabash/ios/device/ipad_1x_2x_mixin.rb +253 -0
- data/lib/calabash/ios/device/keyboard_mixin.rb +2 -0
- data/lib/calabash/ios/device/rotation_mixin.rb +1 -0
- data/lib/calabash/ios/device/routes/condition_route_mixin.rb +1 -0
- data/lib/calabash/ios/device/routes/map_route_mixin.rb +1 -0
- data/lib/calabash/ios/device/routes/response_parser.rb +1 -0
- data/lib/calabash/ios/device/routes/uia_route_mixin.rb +44 -6
- data/lib/calabash/ios/device/text_mixin.rb +2 -0
- data/lib/calabash/ios/device/uia_keyboard_mixin.rb +9 -0
- data/lib/calabash/ios/device/uia_mixin.rb +1 -0
- data/lib/calabash/ios/device.rb +1 -0
- data/lib/calabash/ios/interactions.rb +30 -1
- data/lib/calabash/ios/runtime.rb +8 -0
- data/lib/calabash/ios.rb +2 -0
- data/lib/calabash/life_cycle.rb +19 -2
- data/lib/calabash/location.rb +2 -0
- data/lib/calabash/page.rb +13 -0
- data/lib/calabash/patch.rb +1 -0
- data/lib/calabash/utility.rb +4 -4
- data/lib/calabash/version.rb +1 -1
- data/lib/calabash/wait.rb +4 -0
- data/lib/calabash.rb +6 -4
- metadata +113 -112
- data/lib/calabash/android/scroll.rb +0 -5
@@ -0,0 +1,412 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Calabash
|
4
|
+
module IOS
|
5
|
+
|
6
|
+
# A collection of methods for interacting with UIDatePicker.
|
7
|
+
module DatePicker
|
8
|
+
|
9
|
+
# The API has been tested in various time zones and tested
|
10
|
+
# once while crossing the international date line (on a boat).
|
11
|
+
# With that said, the API makes some assumptions about locales
|
12
|
+
# and time zones. It is possible to customize the ruby date
|
13
|
+
# format and Objective-C date format to get the behavior you
|
14
|
+
# need. You will need to monkey patch the following methods:
|
15
|
+
#
|
16
|
+
# * date_picker_ruby_date_format
|
17
|
+
# * date_picker_objc_date_format
|
18
|
+
#
|
19
|
+
# Before going down this path, we recommend that you ask for
|
20
|
+
# advice on the Calabash support channels.
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
# Provided for monkey patching, but not part of the public API.
|
24
|
+
def date_picker_ruby_date_format
|
25
|
+
RUBY_DATE_AND_TIME_FMT
|
26
|
+
end
|
27
|
+
|
28
|
+
# @!visibility private
|
29
|
+
# Provided for monkey patching, but not part of the public API.
|
30
|
+
def date_picker_objc_date_format
|
31
|
+
OBJC_DATE_AND_TIME_FMT
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!visibility private
|
35
|
+
# Returns the picker mode of the first UIDatePicker match by `query`.
|
36
|
+
#
|
37
|
+
# @see #time_mode?
|
38
|
+
# @see #date_mode?
|
39
|
+
# @see #date_and_time_mode?
|
40
|
+
# @see #countdown_mode?
|
41
|
+
#
|
42
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
43
|
+
# to find UIDatePickers.
|
44
|
+
# @return [String] Returns the picker mode which will be one of
|
45
|
+
# `{'0', '1', '2', '3'}`
|
46
|
+
# @raise [RuntimeError] If no picker can be found.
|
47
|
+
# @raise [RuntimeError] If an unknown mode is returned.
|
48
|
+
# @raise [RuntimeError] If first view matched by query does not responde
|
49
|
+
# to 'datePickerMode'.
|
50
|
+
def date_picker_mode(query)
|
51
|
+
Query.ensure_valid_query(query)
|
52
|
+
|
53
|
+
message = "Timed out waiting for picker with #{query}"
|
54
|
+
mode = nil
|
55
|
+
|
56
|
+
wait_for(message) do
|
57
|
+
result = query(query, :datePickerMode)
|
58
|
+
if result.empty?
|
59
|
+
false
|
60
|
+
else
|
61
|
+
mode = result.first
|
62
|
+
if [0, 1, 2, 3].include?(mode)
|
63
|
+
mode
|
64
|
+
else
|
65
|
+
if mode == '*****'
|
66
|
+
raise RuntimeError,
|
67
|
+
"Query #{query} matched a view that does not respond 'datePickerMode'"
|
68
|
+
else
|
69
|
+
raise RuntimeError,
|
70
|
+
"Query #{query} returned an unknown mode '#{mode}' for 'datePickerMode'"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Is the date picker in time mode?
|
78
|
+
#
|
79
|
+
# @see #time_mode?
|
80
|
+
# @see #date_mode?
|
81
|
+
# @see #date_and_time_mode?
|
82
|
+
# @see #countdown_mode?
|
83
|
+
#
|
84
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
85
|
+
# to find UIDatePickers.
|
86
|
+
#
|
87
|
+
# @return [Boolean] True if the picker is in time mode.
|
88
|
+
#
|
89
|
+
# @raise [RuntimeError] If no picker can be found.
|
90
|
+
# @raise [RuntimeError] If an unknown mode is returned.
|
91
|
+
# @raise [RuntimeError] If first view matched by query does not responde
|
92
|
+
# to 'datePickerMode'.
|
93
|
+
def time_mode?(query)
|
94
|
+
date_picker_mode(query) == UI_DATE_PICKER_MODE_TIME
|
95
|
+
end
|
96
|
+
|
97
|
+
# Is the date picker in date mode?
|
98
|
+
#
|
99
|
+
# @see #time_mode?
|
100
|
+
# @see #date_mode?
|
101
|
+
# @see #date_and_time_mode?
|
102
|
+
# @see #countdown_mode?
|
103
|
+
#
|
104
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
105
|
+
# to find UIDatePickers.
|
106
|
+
#
|
107
|
+
# @return [Boolean] True if the picker is in date mode.
|
108
|
+
#
|
109
|
+
# @raise [RuntimeError] If no picker can be found.
|
110
|
+
# @raise [RuntimeError] If an unknown mode is returned.
|
111
|
+
# @raise [RuntimeError] If first view matched by query does not responde
|
112
|
+
# to 'datePickerMode'.
|
113
|
+
def date_mode?(query)
|
114
|
+
date_picker_mode(query) == UI_DATE_PICKER_MODE_DATE
|
115
|
+
end
|
116
|
+
|
117
|
+
# Is the date picker in date and time mode?
|
118
|
+
#
|
119
|
+
# @see #time_mode?
|
120
|
+
# @see #date_mode?
|
121
|
+
# @see #date_and_time_mode?
|
122
|
+
# @see #countdown_mode?
|
123
|
+
#
|
124
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
125
|
+
# to find UIDatePickers.
|
126
|
+
#
|
127
|
+
# @return [Boolean] True if the picker is in date and time mode.
|
128
|
+
#
|
129
|
+
# @raise [RuntimeError] If no picker can be found.
|
130
|
+
# @raise [RuntimeError] If an unknown mode is returned.
|
131
|
+
# @raise [RuntimeError] If first view matched by query does not responde
|
132
|
+
# to 'datePickerMode'.
|
133
|
+
def date_and_time_mode?(query)
|
134
|
+
date_picker_mode(query) == UI_DATE_PICKER_MODE_DATE_AND_TIME
|
135
|
+
end
|
136
|
+
|
137
|
+
# Is the date picker in countdown mode?
|
138
|
+
#
|
139
|
+
# @see #time_mode?
|
140
|
+
# @see #date_mode?
|
141
|
+
# @see #date_and_time_mode?
|
142
|
+
# @see #countdown_mode?
|
143
|
+
#
|
144
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
145
|
+
# to find UIDatePickers.
|
146
|
+
#
|
147
|
+
# @return [Boolean] True if the picker is in countdown mode.
|
148
|
+
#
|
149
|
+
# @raise [RuntimeError] If no picker can be found.
|
150
|
+
# @raise [RuntimeError] If an unknown mode is returned.
|
151
|
+
# @raise [RuntimeError] If first view matched by query does not responde
|
152
|
+
# to 'datePickerMode'.
|
153
|
+
def countdown_mode?(query)
|
154
|
+
date_picker_mode(query) == UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER
|
155
|
+
end
|
156
|
+
|
157
|
+
# The maximum date for a picker. If there is no maximum date, this
|
158
|
+
# method returns nil.
|
159
|
+
#
|
160
|
+
# @note
|
161
|
+
# From the Apple docs:
|
162
|
+
# `The property is an NSDate object or nil (the default)`.
|
163
|
+
#
|
164
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
165
|
+
# to find UIDatePickers.
|
166
|
+
#
|
167
|
+
# @return [DateTime] The maximum date on the picker or nil if no maximum
|
168
|
+
# exists
|
169
|
+
#
|
170
|
+
# @raise [RuntimeError] If the picker is in countdown mode.
|
171
|
+
# @raise [RuntimeError] If no picker can be found.
|
172
|
+
# @raise [RuntimeError] If the date returned by the server cannot be
|
173
|
+
# converted to a DateTime object.
|
174
|
+
def maximum_date_time_from_picker(query)
|
175
|
+
Query.ensure_valid_query(query)
|
176
|
+
|
177
|
+
wait_for_view(query)
|
178
|
+
|
179
|
+
if countdown_mode?(query)
|
180
|
+
fail('Countdown pickers do not have a maximum date.')
|
181
|
+
end
|
182
|
+
|
183
|
+
result = query(query, :maximumDate)
|
184
|
+
|
185
|
+
if result.empty?
|
186
|
+
fail("Expected '#{query}' to return a visible UIDatePicker")
|
187
|
+
else
|
188
|
+
if result.first.nil?
|
189
|
+
nil
|
190
|
+
else
|
191
|
+
date_str = result.first
|
192
|
+
begin
|
193
|
+
date_time = DateTime.parse(date_str)
|
194
|
+
rescue TypeError, ArgumentError => _
|
195
|
+
raise RuntimeError,
|
196
|
+
"Could not convert string '#{date_str}' into a valid DateTime object"
|
197
|
+
end
|
198
|
+
date_time
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# The minimum date for a picker. If there is no minimum date, this
|
204
|
+
# method returns nil.
|
205
|
+
#
|
206
|
+
# @note
|
207
|
+
# From the Apple docs:
|
208
|
+
# `The property is an NSDate object or nil (the default)`.
|
209
|
+
#
|
210
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
211
|
+
# to find UIDatePickers.
|
212
|
+
#
|
213
|
+
# @return [DateTime] The minimum date on the picker or nil if no minimum
|
214
|
+
# exists
|
215
|
+
#
|
216
|
+
# @raise [RuntimeError] If the picker is in countdown mode.
|
217
|
+
# @raise [RuntimeError] If no picker can be found.
|
218
|
+
# @raise [RuntimeError] If the date returned by the server cannot be
|
219
|
+
# converted to a DateTime object.
|
220
|
+
def minimum_date_time_from_picker(query)
|
221
|
+
Query.ensure_valid_query(query)
|
222
|
+
|
223
|
+
wait_for_view(query)
|
224
|
+
|
225
|
+
if countdown_mode?(query)
|
226
|
+
fail('Countdown pickers do not have a minimum date.')
|
227
|
+
end
|
228
|
+
|
229
|
+
result = query(query, :minimumDate)
|
230
|
+
|
231
|
+
if result.empty?
|
232
|
+
fail("Expected '#{query}' to return a visible UIDatePicker")
|
233
|
+
else
|
234
|
+
if result.first.nil?
|
235
|
+
nil
|
236
|
+
else
|
237
|
+
date_str = result.first
|
238
|
+
begin
|
239
|
+
date_time = DateTime.parse(date_str)
|
240
|
+
rescue TypeError, ArgumentError => _
|
241
|
+
raise RuntimeError,
|
242
|
+
"Could not convert string '#{date_str}' into a valid DateTime object"
|
243
|
+
end
|
244
|
+
date_time
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Returns the date and time from the picker as DateTime object.
|
250
|
+
#
|
251
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
252
|
+
# to find UIDatePickers.
|
253
|
+
#
|
254
|
+
# @return [DateTime] The date on the picker
|
255
|
+
#
|
256
|
+
# @raise [RuntimeError] If the picker is in countdown mode.
|
257
|
+
# @raise [RuntimeError] If no picker can be found.
|
258
|
+
# @raise [RuntimeError] If the date returned by the server cannot be
|
259
|
+
# converted to a DateTime object.
|
260
|
+
def date_time_from_picker(query)
|
261
|
+
Query.ensure_valid_query(query)
|
262
|
+
|
263
|
+
wait_for_view(query)
|
264
|
+
|
265
|
+
if countdown_mode?(query)
|
266
|
+
fail('This method is available for pickers in countdown mode.')
|
267
|
+
end
|
268
|
+
|
269
|
+
result = query(query, :date)
|
270
|
+
|
271
|
+
if result.empty?
|
272
|
+
fail("Expected '#{query}' to return a visible UIDatePicker")
|
273
|
+
else
|
274
|
+
if result.first.nil?
|
275
|
+
nil
|
276
|
+
else
|
277
|
+
date_str = result.first
|
278
|
+
date_time = DateTime.parse(date_str)
|
279
|
+
if date_time.nil?
|
280
|
+
raise RuntimeError,
|
281
|
+
"Could not convert string '#{date_str}' into a valid DateTime object"
|
282
|
+
end
|
283
|
+
date_time
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Sets the date and time on the _first_ UIDatePicker matched by
|
289
|
+
# `query`.
|
290
|
+
#
|
291
|
+
# This method is not valid for UIDatePickers in _countdown_ mode.
|
292
|
+
#
|
293
|
+
# @param [DateTime] date_time The date and time you want to change to.
|
294
|
+
#
|
295
|
+
# @raise [RuntimeError] If `query` does match exactly one picker.
|
296
|
+
# @raise [RuntimeError] If `query` matches a picker in countdown mode.
|
297
|
+
# @raise [RuntimeError] If the target date is greater than the picker's
|
298
|
+
# maximum date.
|
299
|
+
# @raise [RuntimeError] If the target date is less than the picker's
|
300
|
+
# minimum date
|
301
|
+
# @raise [ArgumentError] If the target date is not a DateTime instance.
|
302
|
+
def picker_set_date_time(date_time)
|
303
|
+
picker_set_date_time_in("UIDatePicker index:0", date_time)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Sets the date and time on the _first_ UIDatePicker matched by
|
307
|
+
# `query`.
|
308
|
+
#
|
309
|
+
# This method is not valid for UIDatePickers in _countdown_ mode.
|
310
|
+
#
|
311
|
+
# An error will be raised if more than on view is matched by `query`.
|
312
|
+
#
|
313
|
+
# To avoid matching more than one UIPickerView or subclass:
|
314
|
+
# * Make the query more specific: "UIPickerView marked:'alarm'"
|
315
|
+
# * Use the index language feature: "UIPickerView index:0"
|
316
|
+
# * Query by custom class: "view:'MyPickerView'"
|
317
|
+
#
|
318
|
+
# @param [String, Hash, Calabash::Query] query A query that can be used
|
319
|
+
# to find UIDatePickers.
|
320
|
+
# @param [DateTime] date_time The date and time you want to change to.
|
321
|
+
#
|
322
|
+
# @raise [RuntimeError] If `query` does match exactly one picker.
|
323
|
+
# @raise [RuntimeError] If `query` matches a picker in countdown mode.
|
324
|
+
# @raise [RuntimeError] If the target date is greater than the picker's
|
325
|
+
# maximum date.
|
326
|
+
# @raise [RuntimeError] If the target date is less than the picker's
|
327
|
+
# minimum date
|
328
|
+
# @raise [ArgumentError] If the target date is not a DateTime instance.
|
329
|
+
def picker_set_date_time_in(query, date_time)
|
330
|
+
unless date_time.is_a?(DateTime)
|
331
|
+
raise ArgumentError,
|
332
|
+
"Date time argument '#{date_time}' must be a DateTime but found '#{date_time.class}'"
|
333
|
+
end
|
334
|
+
|
335
|
+
Query.ensure_valid_query(query)
|
336
|
+
|
337
|
+
message = "Timed out waiting for UIDatePicker with '#{query}'"
|
338
|
+
|
339
|
+
wait_for(message) do
|
340
|
+
result = query(query)
|
341
|
+
if result.length > 1
|
342
|
+
fail("Query '#{query}' matched more than on UIDatePicker")
|
343
|
+
else
|
344
|
+
!result.empty?
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
if countdown_mode?(query)
|
349
|
+
message =
|
350
|
+
[
|
351
|
+
"Query '#{query}' matched a picker in countdown mode.",
|
352
|
+
'Setting the date or time on a countdown picker is not supported'
|
353
|
+
].join("\n")
|
354
|
+
fail(message)
|
355
|
+
end
|
356
|
+
|
357
|
+
minimum_date = minimum_date_time_from_picker(query)
|
358
|
+
if !minimum_date.nil? && minimum_date > date_time
|
359
|
+
message = [
|
360
|
+
"Cannot set the date on the picker matched by '#{query}'",
|
361
|
+
"Picker minimum date: '#{minimum_date}'",
|
362
|
+
" Date to change to: '#{date_time}'",
|
363
|
+
"Target date comes before the minimum date."].join("\n")
|
364
|
+
fail(message)
|
365
|
+
end
|
366
|
+
|
367
|
+
maximum_date = maximum_date_time_from_picker(query)
|
368
|
+
if !maximum_date.nil? && maximum_date < date_time
|
369
|
+
message = [
|
370
|
+
"Cannot set the date on the picker matched by '#{query}'",
|
371
|
+
"Picker maximum date: '#{maximum_date}'",
|
372
|
+
" Date to change to: '#{date_time}'",
|
373
|
+
"Target date comes after the maximum date."].join("\n")
|
374
|
+
fail(message)
|
375
|
+
end
|
376
|
+
|
377
|
+
ruby_format = date_picker_ruby_date_format
|
378
|
+
objc_format = date_picker_objc_date_format
|
379
|
+
target_date_string = date_time.strftime(ruby_format).squeeze(' ').strip
|
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)
|
389
|
+
end
|
390
|
+
|
391
|
+
private
|
392
|
+
|
393
|
+
# @!visibility private
|
394
|
+
OBJC_DATE_AND_TIME_FMT = 'yyyy_MM_dd_HH_mm'
|
395
|
+
|
396
|
+
# @!visibility private
|
397
|
+
RUBY_DATE_AND_TIME_FMT = '%Y_%m_%d_%H_%M'
|
398
|
+
|
399
|
+
# UIDatePicker modes
|
400
|
+
|
401
|
+
# @!visibility private
|
402
|
+
UI_DATE_PICKER_MODE_TIME = 0
|
403
|
+
# @!visibility private
|
404
|
+
UI_DATE_PICKER_MODE_DATE = 1
|
405
|
+
# @!visibility private
|
406
|
+
UI_DATE_PICKER_MODE_DATE_AND_TIME = 2
|
407
|
+
# @!visibility private
|
408
|
+
UI_DATE_PICKER_MODE_COUNT_DOWN_TIMER = 3
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
@@ -19,7 +19,7 @@ module Calabash
|
|
19
19
|
include Calabash::IOS::UIAKeyboardMixin
|
20
20
|
include Calabash::IOS::TextMixin
|
21
21
|
include Calabash::IOS::UIAMixin
|
22
|
-
|
22
|
+
include Calabash::IOS::IPadMixin
|
23
23
|
include Calabash::IOS::GesturesMixin
|
24
24
|
|
25
25
|
attr_reader :run_loop
|
@@ -339,6 +339,10 @@ module Calabash
|
|
339
339
|
else
|
340
340
|
raise "Invalid application #{application} for iOS platform."
|
341
341
|
end
|
342
|
+
|
343
|
+
# @todo Get the language code from the server!
|
344
|
+
ensure_ipad_emulation_1x
|
345
|
+
|
342
346
|
{
|
343
347
|
:device => self,
|
344
348
|
:application => application,
|
@@ -0,0 +1,253 @@
|
|
1
|
+
module Calabash
|
2
|
+
module IOS
|
3
|
+
# @!visibility private
|
4
|
+
# Contains methods for interacting with the iPad.
|
5
|
+
module IPadMixin
|
6
|
+
|
7
|
+
# @!visibility private
|
8
|
+
# Provides methods to interact with the 1x and 2x buttons that appear
|
9
|
+
# when an iPhone-only app is emulated on an iPad. Calabash cannot
|
10
|
+
# interact with these apps in 2x mode because the touch coordinates
|
11
|
+
# cannot be reliably translated from normal iPhone dimensions to the
|
12
|
+
# emulated dimensions.
|
13
|
+
#
|
14
|
+
# On iOS < 7, an app _remembered_ its last 1x/2x scale so when it
|
15
|
+
# reopened the previous scale would be the same as when it closed. This
|
16
|
+
# meant you could manually set the scale once to 1x and never have to
|
17
|
+
# interact with the scale button again.
|
18
|
+
#
|
19
|
+
# On iOS > 7, the default behavior is that all emulated apps open at 2x
|
20
|
+
# regardless of their previous scale.
|
21
|
+
#
|
22
|
+
# @note In order to use this class, you must allow Calabash to launch
|
23
|
+
# your app with instruments.
|
24
|
+
class Emulation
|
25
|
+
|
26
|
+
# @!visibility private
|
27
|
+
#
|
28
|
+
# Maintainers: when adding a localization, please notice that
|
29
|
+
# the keys and values are semantically reversed.
|
30
|
+
#
|
31
|
+
# you should read the hash as:
|
32
|
+
#
|
33
|
+
# ```
|
34
|
+
# :emulated_1x <= what button is showing when the app is emulated at 2X?
|
35
|
+
# :emulated_2x <= what button is showing when the app is emulated at 1X?
|
36
|
+
# ```
|
37
|
+
#
|
38
|
+
# @todo Once we have localizations wired up, we can use these keys
|
39
|
+
#
|
40
|
+
# These pull requests describe how to find a key/value pairs from the
|
41
|
+
# on-disk accessibility bundles for _keyboards_ but we can use the same
|
42
|
+
# strategy to look up the Zoom button localizations.
|
43
|
+
#
|
44
|
+
# * https://github.com/calabash/run_loop/pull/197
|
45
|
+
# * https://github.com/calabash/calabash-ios-server/pull/221
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# fullscreen.zoom => 2x => 'Switch to full screen mode'
|
49
|
+
# normal.zoom => 1x => 'Switch to normal mode'
|
50
|
+
#
|
51
|
+
# # We will still need query windows.
|
52
|
+
# uia("UIATarget.localTarget().frontMostApp().windows()[2].elements()[0].label()")
|
53
|
+
IPAD_1X_2X_BUTTON_LABELS = {
|
54
|
+
:en => {:emulated_1x => '2X',
|
55
|
+
:emulated_2x => '1X'}
|
56
|
+
}
|
57
|
+
|
58
|
+
# @!visibility private
|
59
|
+
# @!attribute [r] scale
|
60
|
+
# The current 1X or 2X scale represented as a Symbol.
|
61
|
+
#
|
62
|
+
# @return [Symbol] Returns this emulation's scale. Will be one of
|
63
|
+
# `{:emulated_1x | :emulated_2x}`.
|
64
|
+
attr_reader :scale
|
65
|
+
|
66
|
+
# @!visibility private
|
67
|
+
# @!attribute [r] lang_code
|
68
|
+
# The Apple compatible language code for determining the accessibility
|
69
|
+
# label of the 1X and 2X buttons.
|
70
|
+
#
|
71
|
+
# @return [Symbol] Returns the language code of this emulation.
|
72
|
+
attr_reader :lang_code
|
73
|
+
|
74
|
+
# @!visibility private
|
75
|
+
# @!attribute [r] device
|
76
|
+
# A handle on the default device.
|
77
|
+
attr_reader :device
|
78
|
+
|
79
|
+
# @!visibility private
|
80
|
+
# A private instance variable for storing this emulation's 1X/2X button
|
81
|
+
# names. The value will be set at runtime based on the language code
|
82
|
+
# that is passed the initializer.
|
83
|
+
@button_names_hash = nil
|
84
|
+
|
85
|
+
# @!visibility private
|
86
|
+
# Creates a new Emulation.
|
87
|
+
# @param [Symbol] lang_code an Apple compatible language code
|
88
|
+
# @return [Emulation] Returns an emulation that is ready for action!
|
89
|
+
def initialize (device, lang_code=:en)
|
90
|
+
@button_names_hash = IPAD_1X_2X_BUTTON_LABELS[lang_code]
|
91
|
+
if @button_names_hash.nil?
|
92
|
+
raise "could not find 1X/2X buttons for language code '#{lang_code}'"
|
93
|
+
end
|
94
|
+
|
95
|
+
@device = device
|
96
|
+
@lang_code = lang_code
|
97
|
+
@scale = _internal_ipad_emulation_scale
|
98
|
+
end
|
99
|
+
|
100
|
+
# @!visibility private
|
101
|
+
def tap_ipad_scale_button
|
102
|
+
key = @scale
|
103
|
+
name = @button_names_hash[key]
|
104
|
+
|
105
|
+
query_args =
|
106
|
+
[
|
107
|
+
[
|
108
|
+
:view, {:marked => "#{name}"}
|
109
|
+
]
|
110
|
+
]
|
111
|
+
|
112
|
+
device.uia_query_then_make_javascript_calls(:queryElWindows, query_args, :tap)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# @!visibility private
|
118
|
+
def _internal_ipad_emulation_scale
|
119
|
+
hash = @button_names_hash
|
120
|
+
val = nil
|
121
|
+
hash.values.each do |button_name|
|
122
|
+
query_args =
|
123
|
+
[
|
124
|
+
:view, {:marked => button_name}
|
125
|
+
]
|
126
|
+
|
127
|
+
button_exists = device.uia_serialize_and_call(:queryElWindows,
|
128
|
+
query_args)
|
129
|
+
if button_exists
|
130
|
+
result = device.uia_query_then_make_javascript_calls(:queryElWindows,
|
131
|
+
[query_args],
|
132
|
+
:name)
|
133
|
+
if result == button_name
|
134
|
+
val = button_name
|
135
|
+
break
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
if val.nil?
|
141
|
+
raise "Could not find iPad scale button with '#{hash.values}'"
|
142
|
+
end
|
143
|
+
|
144
|
+
if val == hash[:emulated_1x]
|
145
|
+
:emulated_1x
|
146
|
+
elsif val == hash[:emulated_2x]
|
147
|
+
:emulated_2x
|
148
|
+
else
|
149
|
+
raise "Unrecognized emulation scale '#{val}'"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# @!visibility private
|
155
|
+
# Ensures that iPhone apps emulated on an iPad are displayed at scale.
|
156
|
+
#
|
157
|
+
# @note It is recommended that clients call this `ensure_ipad_emulation_1x`
|
158
|
+
# instead of this method.
|
159
|
+
#
|
160
|
+
# @note If this is not an iPhone app emulated on an iPad, then calling
|
161
|
+
# this method has no effect.
|
162
|
+
#
|
163
|
+
# @note In order to use this method, you must allow Calabash to launch
|
164
|
+
# your app with instruments.
|
165
|
+
#
|
166
|
+
# Starting in iOS 7, iPhone apps emulated on the iPad always launch at 2x.
|
167
|
+
# calabash cannot currently interact with such apps in 2x mode (trust us,
|
168
|
+
# we've tried).
|
169
|
+
#
|
170
|
+
# @see #ensure_ipad_emulation_1x
|
171
|
+
#
|
172
|
+
# @param [Symbol] scale the desired scale - must be `:emulated_1x` or
|
173
|
+
# `:emulated_2x`
|
174
|
+
#
|
175
|
+
# @param [Hash] opts optional arguments to control the interaction with
|
176
|
+
# the 1X/2X buttons
|
177
|
+
#
|
178
|
+
# @option opts [Symbol] :lang_code (:en) an Apple compatible
|
179
|
+
# language code
|
180
|
+
# @option opts [Symbol] :wait_after_touch (0.4) how long to
|
181
|
+
# wait _after_ the scale button is touched
|
182
|
+
#
|
183
|
+
# @return [void]
|
184
|
+
#
|
185
|
+
# @raise [RuntimeError] If the app was not launched with instruments.
|
186
|
+
# @raise [RuntimeError] If an invalid `scale` is passed.
|
187
|
+
# @raise [RuntimeError] If an unknown language code is passed.
|
188
|
+
# @raise [RuntimeError] If the scale button cannot be touched.
|
189
|
+
def ensure_ipad_emulation_scale(scale, opts={})
|
190
|
+
return unless iphone_app_emulated_on_ipad?
|
191
|
+
|
192
|
+
allowed = [:emulated_1x, :emulated_2x]
|
193
|
+
unless allowed.include?(scale)
|
194
|
+
raise "Scale '#{scale}' is not one of '#{allowed}' allowed args"
|
195
|
+
end
|
196
|
+
|
197
|
+
default_opts = {:lang_code => :en,
|
198
|
+
:wait_after_touch => 0.4}
|
199
|
+
merged_opts = default_opts.merge(opts)
|
200
|
+
|
201
|
+
obj = Emulation.new(self, merged_opts[:lang_code])
|
202
|
+
|
203
|
+
actual_scale = obj.scale
|
204
|
+
|
205
|
+
if actual_scale != scale
|
206
|
+
obj.tap_ipad_scale_button
|
207
|
+
end
|
208
|
+
|
209
|
+
sleep(merged_opts[:wait_after_touch])
|
210
|
+
end
|
211
|
+
|
212
|
+
# @!visibility private
|
213
|
+
# Ensures that iPhone apps emulated on an iPad are displayed at `1X`.
|
214
|
+
#
|
215
|
+
# @note If this is not an iPhone app emulated on an iPad, then calling
|
216
|
+
# this method has no effect.
|
217
|
+
#
|
218
|
+
# @note In order to use this method, you must allow Calabash to launch
|
219
|
+
# your app with instruments.
|
220
|
+
#
|
221
|
+
# Starting in iOS 7, iPhone apps emulated on the iPad always launch at 2x.
|
222
|
+
# calabash cannot currently interact with such apps in 2x mode (trust us,
|
223
|
+
# we've tried).
|
224
|
+
#
|
225
|
+
# @param [Hash] opts optional arguments to control the interaction with
|
226
|
+
# the 1X/2X buttons
|
227
|
+
#
|
228
|
+
# @option opts [Symbol] :lang_code (:en) an Apple compatible
|
229
|
+
# language code
|
230
|
+
# @option opts [Symbol] :wait_after_touch (0.4) how long to
|
231
|
+
# wait _after_ the scale button is touched
|
232
|
+
#
|
233
|
+
# @return [void]
|
234
|
+
#
|
235
|
+
# @raise [RuntimeError] If the app was not launched with instruments.
|
236
|
+
# @raise [RuntimeError] If an unknown language code is passed.
|
237
|
+
# @raise [RuntimeError] If the scale button cannot be touched.
|
238
|
+
def ensure_ipad_emulation_1x(opts={})
|
239
|
+
ensure_ipad_emulation_scale(:emulated_1x, opts)
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
# @!visibility private
|
244
|
+
# Ensures iPhone apps running on an iPad are emulated at 2X
|
245
|
+
#
|
246
|
+
# You should never need to call this function - Calabash cannot interact
|
247
|
+
# with iPhone apps emulated on the iPad in 2x mode.
|
248
|
+
def _ensure_ipad_emulation_2x(opts={})
|
249
|
+
ensure_ipad_emulation_scale(:emulated_2x, opts)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|