calabash-cucumber 0.9.163.pre11 → 0.9.163
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/calabash-ios-setup.rb +2 -2
- data/calabash-cucumber.gemspec +2 -2
- data/features/step_definitions/calabash_steps.rb +7 -10
- data/lib/calabash-cucumber.rb +1 -2
- data/lib/calabash-cucumber/core.rb +31 -34
- data/lib/calabash-cucumber/device.rb +8 -5
- data/lib/calabash-cucumber/environment_helpers.rb +205 -0
- data/lib/calabash-cucumber/failure_helpers.rb +9 -6
- data/lib/calabash-cucumber/ios7_operations.rb +8 -11
- data/lib/calabash-cucumber/keyboard_helpers.rb +628 -67
- data/lib/calabash-cucumber/launch/simulator_helper.rb +4 -1
- data/lib/calabash-cucumber/launcher.rb +6 -3
- data/lib/calabash-cucumber/operations.rb +7 -4
- data/lib/calabash-cucumber/playback_helpers.rb +11 -8
- data/lib/calabash-cucumber/rotation_helpers.rb +8 -8
- data/lib/calabash-cucumber/status_bar_helpers.rb +15 -3
- data/lib/calabash-cucumber/uia.rb +13 -6
- data/lib/calabash-cucumber/version.rb +2 -2
- metadata +9 -8
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module Calabash
|
2
4
|
module Cucumber
|
3
5
|
module FailureHelpers
|
@@ -8,12 +10,12 @@ module Calabash
|
|
8
10
|
|
9
11
|
@@screenshot_count ||= 0
|
10
12
|
res = http({:method => :get, :path => 'screenshot'})
|
11
|
-
prefix = prefix || ENV['SCREENSHOT_PATH'] ||
|
13
|
+
prefix = prefix || ENV['SCREENSHOT_PATH'] || ''
|
12
14
|
if name.nil?
|
13
|
-
name =
|
15
|
+
name = 'screenshot'
|
14
16
|
else
|
15
|
-
if File.extname(name).downcase ==
|
16
|
-
name = name.split(
|
17
|
+
if File.extname(name).downcase == '.png'
|
18
|
+
name = name.split('.png')[0]
|
17
19
|
end
|
18
20
|
end
|
19
21
|
|
@@ -27,7 +29,8 @@ module Calabash
|
|
27
29
|
|
28
30
|
def screenshot_embed(options={:prefix => nil, :name => nil, :label => nil})
|
29
31
|
path = screenshot(options)
|
30
|
-
|
32
|
+
filename = options[:label] || File.basename(path)
|
33
|
+
embed(path, 'image/png', filename)
|
31
34
|
end
|
32
35
|
|
33
36
|
def screenshot_and_raise(msg, options={:prefix => nil, :name => nil, :label => nil})
|
@@ -35,7 +38,7 @@ module Calabash
|
|
35
38
|
raise(msg)
|
36
39
|
end
|
37
40
|
|
38
|
-
def fail(msg=
|
41
|
+
def fail(msg='Error. Check log for details.', options={:prefix => nil, :name => nil, :label => nil})
|
39
42
|
screenshot_and_raise(msg, options)
|
40
43
|
end
|
41
44
|
|
@@ -2,22 +2,22 @@ require 'calabash-cucumber/launcher'
|
|
2
2
|
require 'calabash-cucumber/uia'
|
3
3
|
require 'calabash-cucumber/actions/instruments_actions'
|
4
4
|
require 'calabash-cucumber/actions/playback_actions'
|
5
|
+
require 'calabash-cucumber/environment_helpers'
|
6
|
+
|
7
|
+
|
5
8
|
|
6
9
|
module Calabash
|
7
10
|
module Cucumber
|
8
11
|
module IOS7Operations
|
9
12
|
include Calabash::Cucumber::UIA
|
13
|
+
include Calabash::Cucumber::EnvironmentHelpers
|
10
14
|
|
11
|
-
|
12
|
-
launcher = @calabash_launcher || Calabash::Cucumber::Launcher.launcher_if_used
|
13
|
-
ENV['OS']=='ios7' || (launcher && launcher.device && launcher.device.ios7?)
|
14
|
-
end
|
15
|
-
|
15
|
+
# todo deprecate the Calabash::Cucumber::IOS7Operations
|
16
16
|
|
17
|
-
|
18
|
-
# Deprecated - abstracted into
|
19
|
-
# actions/instruments_actions.rb - actions that can be performed under instruments
|
17
|
+
# <b>DEPRECATED</b>
|
20
18
|
#
|
19
|
+
# abstracted into <tt>actions/instruments_actions.rb</tt> - actions that
|
20
|
+
# can be performed under instruments
|
21
21
|
def touch_ios7(options)
|
22
22
|
ui_query = options[:query]
|
23
23
|
offset = options[:offset]
|
@@ -118,9 +118,6 @@ module Calabash
|
|
118
118
|
[el_to_swipe]
|
119
119
|
end
|
120
120
|
end
|
121
|
-
|
122
|
-
|
123
|
-
|
124
121
|
end
|
125
122
|
end
|
126
123
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'calabash-cucumber/core'
|
2
2
|
require 'calabash-cucumber/tests_helpers'
|
3
3
|
require 'calabash-cucumber/playback_helpers'
|
4
|
+
require 'calabash-cucumber/environment_helpers'
|
4
5
|
|
5
6
|
module Calabash
|
6
7
|
module Cucumber
|
7
8
|
module KeyboardHelpers
|
8
9
|
include Calabash::Cucumber::TestsHelpers
|
9
|
-
include Calabash::Cucumber::PlaybackHelpers
|
10
10
|
|
11
11
|
KEYPLANE_NAMES = {
|
12
12
|
:small_letters => 'small-letters',
|
@@ -18,31 +18,228 @@ module Calabash
|
|
18
18
|
|
19
19
|
|
20
20
|
UIA_SUPPORTED_CHARS = {
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
'Delete' => '\b',
|
22
|
+
'Return' => '\n'
|
23
|
+
# these are not supported yet and I am pretty sure that they
|
24
|
+
# cannot be touched by passing an escaped character and instead
|
25
|
+
# the must be found using UIAutomation calls. -jmoody
|
26
|
+
#'Dictation' => nil,
|
27
|
+
#'Shift' => nil,
|
28
|
+
#'International' => nil,
|
29
|
+
#'More' => nil,
|
27
30
|
}
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
|
32
|
+
|
33
|
+
# returns a query string for detecting a keyboard
|
34
|
+
def _qstr_for_keyboard
|
35
|
+
"view:'UIKBKeyplaneView'"
|
36
|
+
end
|
37
|
+
|
38
|
+
# returns +true+ if a +docked+ keyboard is visible.
|
39
|
+
#
|
40
|
+
# a +docked+ keyboard is pinned to the bottom of the view.
|
41
|
+
#
|
42
|
+
# keyboards on the iPhone and iPod are +docked+.
|
43
|
+
def docked_keyboard_visible?
|
44
|
+
res = query(_qstr_for_keyboard).first
|
45
|
+
return false if res.nil?
|
46
|
+
|
47
|
+
return true if device_family_iphone?
|
48
|
+
|
49
|
+
# ipad
|
50
|
+
rect = res['rect']
|
51
|
+
o = status_bar_orientation.to_sym
|
52
|
+
case o
|
53
|
+
when :left then
|
54
|
+
rect['center_x'] == 592 and rect['center_y'] == 512
|
55
|
+
when :right then
|
56
|
+
rect['center_x'] == 176 and rect['center_y'] == 512
|
57
|
+
when :up then
|
58
|
+
rect['center_x'] == 384 and rect['center_y'] == 132
|
59
|
+
when :down then
|
60
|
+
rect['center_x'] == 384 and rect['center_y'] == 892
|
61
|
+
else
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# returns +true+ if an +undocked+ keyboard is visible.
|
68
|
+
#
|
69
|
+
# a +undocked+ keyboard is floats in the middle of the view
|
70
|
+
#
|
71
|
+
# returns +false+ if the device is not an iPad; all keyboards on the
|
72
|
+
# iPhone and iPod are +docked+
|
73
|
+
def undocked_keyboard_visible?
|
74
|
+
return false if device_family_iphone?
|
75
|
+
|
76
|
+
res = query(_qstr_for_keyboard).first
|
77
|
+
return false if res.nil?
|
78
|
+
|
79
|
+
not docked_keyboard_visible?
|
80
|
+
end
|
81
|
+
|
82
|
+
# returns +true+ if a +split+ keyboard is visible.
|
83
|
+
#
|
84
|
+
# a +split+ keyboard is floats in the middle of the view and is split to
|
85
|
+
# allow faster thumb typing
|
86
|
+
#
|
87
|
+
# returns +false+ if the device is not an iPad; all keyboards on the
|
88
|
+
# iPhone and iPod are +docked+
|
89
|
+
def split_keyboard_visible?
|
90
|
+
return false if device_family_iphone?
|
91
|
+
query("view:'UIKBKeyView'").count > 0 and
|
92
|
+
element_does_not_exist(_qstr_for_keyboard)
|
93
|
+
end
|
94
|
+
|
95
|
+
# returns true if there is a visible keyboard
|
96
|
+
def keyboard_visible?
|
97
|
+
docked_keyboard_visible? or undocked_keyboard_visible? or split_keyboard_visible?
|
98
|
+
end
|
99
|
+
|
100
|
+
# waits for a keyboard to appear and once it does appear waits for 0.3
|
101
|
+
# seconds
|
102
|
+
#
|
103
|
+
# raises an error if no keyboard appears
|
104
|
+
def wait_for_keyboard(opts={})
|
105
|
+
default_opts = {:timeout_message => 'keyboard did not appear',
|
106
|
+
:post_timeout => 0.3}
|
107
|
+
opts = default_opts.merge(opts)
|
108
|
+
wait_for(opts) do
|
109
|
+
keyboard_visible?
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# <b>DEPRECATED:</b> Use <tt>wait_for_keyboard</tt> instead.
|
114
|
+
def await_keyboard
|
115
|
+
_deprecated('0.9.163', "use 'wait_for_keyboard' instead", :warn)
|
116
|
+
wait_for_keyboard
|
117
|
+
end
|
118
|
+
|
119
|
+
# returns an array of possible ipad keyboard modes
|
120
|
+
def _ipad_keyboard_modes
|
121
|
+
[:docked, :undocked, :split]
|
122
|
+
end
|
123
|
+
|
124
|
+
# returns the keyboard +mode+
|
125
|
+
#
|
126
|
+
# keyboard is pinned to bottom of the view #=> :docked
|
127
|
+
# keyboard is floating in the middle of the view #=> :undocked
|
128
|
+
# keyboard is floating and split #=> :split
|
129
|
+
# no keyboard and :raise_on_no_visible_keyboard == +false+ #=> :unknown
|
130
|
+
#
|
131
|
+
# raises an error if the device is not an iPad
|
132
|
+
#
|
133
|
+
# raises an error if the <tt>:raise_on_no_visible_keyboard</tt> is +true+
|
134
|
+
# (default) and no keyboard is visible
|
135
|
+
#
|
136
|
+
# set <tt>:raise_on_no_visible_keyboard</tt> to +false+ to use in +wait+
|
137
|
+
# functions
|
138
|
+
def ipad_keyboard_mode(opts = {})
|
139
|
+
raise 'the keyboard mode does not exist on the iphone or ipod' if device_family_iphone?
|
140
|
+
|
141
|
+
default_opts = {:raise_on_no_visible_keyboard => true}
|
142
|
+
opts = default_opts.merge(opts)
|
143
|
+
if opts[:raise_on_no_visible_keyboard]
|
144
|
+
screenshot_and_raise 'there is no visible keyboard' unless keyboard_visible?
|
145
|
+
return :docked if docked_keyboard_visible?
|
146
|
+
return :undocked if undocked_keyboard_visible?
|
147
|
+
:split
|
148
|
+
else
|
149
|
+
return :docked if docked_keyboard_visible?
|
150
|
+
return :undocked if undocked_keyboard_visible?
|
151
|
+
return :split if split_keyboard_visible?
|
152
|
+
:unknown
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# ensures that there is a keyboard to enter text
|
157
|
+
#
|
158
|
+
# IMPORTANT will always raise an error when the keyboard is split and
|
159
|
+
# there is no <tt>run_loop</tt> i.e. +UIAutomation+ is not available
|
160
|
+
#
|
161
|
+
# the default options are
|
162
|
+
# :screenshot +true+ raise with a screenshot
|
163
|
+
# :skip +false+ skip any checking (a nop) - used when iterating over
|
164
|
+
# keyplanes for keys
|
165
|
+
def _ensure_can_enter_text(opts={})
|
166
|
+
default_opts = {:screenshot => true,
|
167
|
+
:skip => false}
|
168
|
+
opts = default_opts.merge(opts)
|
169
|
+
return if opts[:skip]
|
170
|
+
|
171
|
+
screenshot = opts[:screenshot]
|
172
|
+
unless keyboard_visible?
|
173
|
+
msg = 'no visible keyboard'
|
174
|
+
if screenshot
|
175
|
+
screenshot_and_raise msg
|
176
|
+
else
|
177
|
+
raise msg
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
if split_keyboard_visible? and uia_not_available?
|
182
|
+
msg = 'cannot type on a split keyboard without launching with Instruments'
|
183
|
+
if screenshot
|
184
|
+
screenshot_and_raise msg
|
185
|
+
else
|
186
|
+
raise msg
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# use keyboard to enter +chr+
|
192
|
+
#
|
193
|
+
# IMPORTANT: use the <tt>POST_ENTER_KEYBOARD</tt> environmental variable
|
194
|
+
# to slow down the typing; adds a wait after each character is touched.
|
195
|
+
# this can fix problems where the typing is too fast and characters are
|
196
|
+
# skipped.
|
197
|
+
#
|
198
|
+
# there are several special 'characters', some of which do not appear on all
|
199
|
+
# keyboards:
|
200
|
+
# * 'Delete'
|
201
|
+
# * 'Return'
|
202
|
+
#
|
203
|
+
# raises error if there is no visible keyboard or the keyboard is not
|
204
|
+
# supported
|
205
|
+
#
|
206
|
+
# use the +should_screenshot+ to control whether or not to raise an error
|
207
|
+
# if +chr+ is not found
|
208
|
+
def keyboard_enter_char(chr, opts={})
|
209
|
+
unless opts.is_a?(Hash)
|
210
|
+
msg = "you should no longer pass a boolean as the second arg; pass {:should_screenshot => '#{opts}'} hash instead"
|
211
|
+
_deprecated('0.9.163', msg, :warn)
|
212
|
+
opts = {:should_screenshot => opts}
|
213
|
+
end
|
214
|
+
|
215
|
+
default_opts = {:should_screenshot => true,
|
216
|
+
# introduce a small wait to avoid skipping characters
|
217
|
+
# keep this as short as possible
|
218
|
+
:wait_after_char => (ENV['POST_ENTER_KEYBOARD'] || 0.05).to_f}
|
219
|
+
|
220
|
+
opts = default_opts.merge(opts)
|
221
|
+
|
222
|
+
should_screenshot = opts[:should_screenshot]
|
223
|
+
_ensure_can_enter_text({:screenshot => should_screenshot,
|
224
|
+
:skip => (not should_screenshot)})
|
225
|
+
|
226
|
+
if uia_available?
|
38
227
|
if chr.length == 1
|
39
228
|
uia_type_string chr
|
40
229
|
else
|
41
230
|
code = UIA_SUPPORTED_CHARS[chr]
|
42
|
-
|
43
|
-
|
231
|
+
|
232
|
+
unless code
|
233
|
+
raise "Char #{chr} is not yet supported in when typing with Instruments"
|
234
|
+
end
|
235
|
+
|
236
|
+
# on iOS 6, the char code is _not_ \b
|
237
|
+
#
|
238
|
+
# as an aside, on iOS 7, the same approach (tap the 'Delete' key) also works
|
239
|
+
if ios6? and code.eql?(UIA_SUPPORTED_CHARS['Delete'])
|
240
|
+
uia("uia.keyboard().keys().firstWithName('Delete').tap()")
|
44
241
|
else
|
45
|
-
|
242
|
+
uia_type_string(code)
|
46
243
|
end
|
47
244
|
end
|
48
245
|
res = {'results' => []}
|
@@ -66,56 +263,116 @@ module Calabash
|
|
66
263
|
sleep(w)
|
67
264
|
end
|
68
265
|
end
|
266
|
+
pause = opts[:wait_after_char]
|
267
|
+
sleep(pause) if pause > 0
|
69
268
|
res['results']
|
70
269
|
end
|
71
270
|
|
72
|
-
|
73
|
-
|
271
|
+
# uses the keyboard to enter +text+
|
272
|
+
#
|
273
|
+
# raises an error if the text cannot be entered
|
274
|
+
def keyboard_enter_text(text)
|
275
|
+
_ensure_can_enter_text
|
276
|
+
if uia_available?
|
277
|
+
uia_type_string(text)
|
278
|
+
else
|
279
|
+
text.each_char do |ch|
|
280
|
+
begin
|
281
|
+
keyboard_enter_char(ch, {:should_screenshot => false})
|
282
|
+
rescue
|
283
|
+
_search_keyplanes_and_enter_char(ch)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# touches the keyboard +action+ key
|
291
|
+
#
|
292
|
+
# the +action+ key depends on the keyboard. some examples include:
|
293
|
+
# * Return
|
294
|
+
# * Next
|
295
|
+
# * Go
|
296
|
+
# * Join
|
297
|
+
# * Search
|
298
|
+
#
|
299
|
+
# not all keyboards have an +action+ key
|
300
|
+
# raises an error if the key cannot be entered
|
301
|
+
def tap_keyboard_action_key
|
302
|
+
if uia_available?
|
74
303
|
uia_type_string '\n'
|
75
304
|
else
|
76
305
|
keyboard_enter_char 'Return'
|
77
306
|
end
|
78
|
-
|
79
307
|
end
|
80
308
|
|
309
|
+
# touches the keyboard +action+ key
|
310
|
+
#
|
311
|
+
# the +action+ key depends on the keyboard.
|
312
|
+
#
|
313
|
+
# some examples include:
|
314
|
+
# * Return
|
315
|
+
# * Next
|
316
|
+
# * Go
|
317
|
+
# * Join
|
318
|
+
# * Search
|
319
|
+
#
|
320
|
+
# not all keyboards have an +action+ key
|
321
|
+
# raises an error if the key cannot be entered
|
322
|
+
def done
|
323
|
+
tap_keyboard_action_key
|
324
|
+
end
|
81
325
|
|
82
|
-
|
326
|
+
# returns the current keyplane
|
327
|
+
def _current_keyplane
|
83
328
|
kp_arr = _do_keyplane(
|
84
|
-
lambda { query("view:'UIKBKeyplaneView'",
|
85
|
-
lambda { query("view:'UIKBKeyplaneView'",
|
329
|
+
lambda { query("view:'UIKBKeyplaneView'", 'keyplane', 'componentName') },
|
330
|
+
lambda { query("view:'UIKBKeyplaneView'", 'keyplane', 'name') })
|
86
331
|
kp_arr.first.downcase
|
87
332
|
end
|
88
333
|
|
89
|
-
|
90
|
-
|
334
|
+
# searches the available keyplanes for +chr+ and if it is found, types it
|
335
|
+
#
|
336
|
+
# this is a recursive function
|
337
|
+
#
|
338
|
+
# IMPORTANT: use the <tt>KEYPLANE_SEARCH_STEP_PAUSE</tt> variable to
|
339
|
+
# control how quickly the next keyplane is searched. increase this value
|
340
|
+
# if you encounter problems with missed keystrokes.
|
341
|
+
#
|
342
|
+
# raises an error if the +chr+ cannot be found
|
343
|
+
def _search_keyplanes_and_enter_char(chr, visited=Set.new)
|
344
|
+
cur_kp = _current_keyplane
|
91
345
|
begin
|
92
|
-
keyboard_enter_char(chr, false)
|
346
|
+
keyboard_enter_char(chr, {:should_screenshot => false})
|
93
347
|
return true #found
|
94
348
|
rescue
|
349
|
+
pause = (ENV['KEYPLANE_SEARCH_STEP_PAUSE'] || 0.2).to_f
|
350
|
+
sleep (pause) if pause > 0
|
351
|
+
|
95
352
|
visited.add(cur_kp)
|
96
353
|
|
97
354
|
#figure out keyplane alternates
|
98
355
|
props = _do_keyplane(
|
99
|
-
lambda { query("view:'UIKBKeyplaneView'",
|
100
|
-
lambda { query("view:'UIKBKeyplaneView'",
|
356
|
+
lambda { query("view:'UIKBKeyplaneView'", 'keyplane', 'properties') },
|
357
|
+
lambda { query("view:'UIKBKeyplaneView'", 'keyplane', 'attributes', 'dict') }
|
101
358
|
).first
|
102
359
|
|
103
360
|
known = KEYPLANE_NAMES.values
|
104
361
|
|
105
362
|
found = false
|
106
|
-
keyplane_selection_keys = [
|
363
|
+
keyplane_selection_keys = ['shift', 'more']
|
107
364
|
keyplane_selection_keys.each do |key|
|
365
|
+
sleep (pause) if pause > 0
|
108
366
|
plane = props["#{key}-alternate"]
|
109
|
-
if
|
110
|
-
|
111
|
-
|
112
|
-
found = search_keyplanes_and_enter_char(chr, visited)
|
367
|
+
if known.member?(plane) and (not visited.member?(plane))
|
368
|
+
keyboard_enter_char(key.capitalize, {:should_screenshot => false})
|
369
|
+
found = _search_keyplanes_and_enter_char(chr, visited)
|
113
370
|
return true if found
|
114
371
|
#not found => try with other keyplane selection key
|
115
372
|
keyplane_selection_keys.delete(key)
|
116
373
|
other_key = keyplane_selection_keys.last
|
117
|
-
keyboard_enter_char(other_key.capitalize, false)
|
118
|
-
found =
|
374
|
+
keyboard_enter_char(other_key.capitalize, {:should_screenshot => false})
|
375
|
+
found = _search_keyplanes_and_enter_char(chr, visited)
|
119
376
|
return true if found
|
120
377
|
end
|
121
378
|
end
|
@@ -123,48 +380,352 @@ module Calabash
|
|
123
380
|
end
|
124
381
|
end
|
125
382
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
383
|
+
# process a keyplane
|
384
|
+
#
|
385
|
+
# raises an error if there is not visible keyplane
|
386
|
+
def _do_keyplane(kbtree_proc, keyplane_proc)
|
387
|
+
desc = query("view:'UIKBKeyplaneView'", 'keyplane')
|
388
|
+
fail('No keyplane (UIKBKeyplaneView keyplane)') if desc.empty?
|
389
|
+
fail('Several keyplanes (UIKBKeyplaneView keyplane)') if desc.count > 1
|
390
|
+
kp_desc = desc.first
|
391
|
+
if /^<UIKBTree/.match(kp_desc)
|
392
|
+
#ios5+
|
393
|
+
kbtree_proc.call
|
394
|
+
elsif /^<UIKBKeyplane/.match(kp_desc)
|
395
|
+
#ios4
|
396
|
+
keyplane_proc.call
|
397
|
+
end
|
130
398
|
end
|
131
399
|
|
132
|
-
#
|
133
|
-
def
|
134
|
-
|
135
|
-
sleep(0.5)
|
400
|
+
# returns a query string for finding the iPad 'Hide keyboard' button
|
401
|
+
def _query_uia_hide_keyboard_button
|
402
|
+
"uia.keyboard().buttons()['Hide keyboard']"
|
136
403
|
end
|
137
404
|
|
138
|
-
|
139
|
-
|
405
|
+
# dismisses a iPad keyboard by touching the 'Hide keyboard' button and waits
|
406
|
+
# for the keyboard to disappear
|
407
|
+
#
|
408
|
+
# raises an error if the device is not an iPad. the dismiss keyboard
|
409
|
+
# key does not exist on the iPhone or iPod
|
410
|
+
def dismiss_ipad_keyboard
|
411
|
+
screenshot_and_raise 'cannot dismiss keyboard on iphone' if device_family_iphone?
|
140
412
|
|
141
|
-
if
|
142
|
-
|
413
|
+
if uia_available?
|
414
|
+
send_uia_command({:command => "#{_query_uia_hide_keyboard_button}.tap()"})
|
143
415
|
else
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
416
|
+
touch(_query_for_keyboard_mode_key)
|
417
|
+
end
|
418
|
+
|
419
|
+
opts = {:timeout_message => 'keyboard did not disappear'}
|
420
|
+
wait_for(opts) do
|
421
|
+
not keyboard_visible?
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
# returns the activation point of the iPad keyboard +mode+ key.
|
426
|
+
#
|
427
|
+
# the +mode+ key is also known as the <tt>Hide keyboard</tt> key.
|
428
|
+
#
|
429
|
+
# raises an error when
|
430
|
+
# * the device is not an iPad
|
431
|
+
# * the app was not launched with instruments i.e. there is no <tt>run_loop</tt>
|
432
|
+
def _point_for_ipad_keyboard_mode_key
|
433
|
+
raise 'the keyboard mode does not exist on the on the iphone' if device_family_iphone?
|
434
|
+
raise 'cannot detect keyboard mode key without launching with instruments' unless uia_available?
|
435
|
+
res = send_uia_command({:command => "#{_query_uia_hide_keyboard_button}.rect()"})
|
436
|
+
origin = res['value']['origin']
|
437
|
+
{:x => origin['x'], :y => origin['y']}
|
438
|
+
|
439
|
+
# this did not work.
|
440
|
+
#size = res['value']['size']
|
441
|
+
#{:x => (origin['x'] + (size['width']/2)), :y => (origin['y'] + (size['height']/2))}
|
442
|
+
end
|
443
|
+
|
444
|
+
|
445
|
+
# returns a query string for touching one of the options that appears when
|
446
|
+
# the iPad +mode+ key is touched and held.
|
447
|
+
#
|
448
|
+
# the +mode+ key is also know as the <tt>Hide keyboard</tt> key.
|
449
|
+
#
|
450
|
+
# valid arguments are:
|
451
|
+
# top_or_bottom :top | :bottom
|
452
|
+
# mode :docked | :undocked | :skipped
|
453
|
+
#
|
454
|
+
# use <tt>_point_for_keyboard_mode_key</tt> if there is a <tt>run_loop</tt>
|
455
|
+
# available
|
456
|
+
#
|
457
|
+
# raises an error when
|
458
|
+
# * the device is not an iPad
|
459
|
+
# * the app was launched with Instruments i.e. there is a <tt>run_loop</tt>
|
460
|
+
# * it is passed invalid arguments
|
461
|
+
def _query_for_touch_for_keyboard_mode_option(top_or_bottom, mode)
|
462
|
+
raise 'the keyboard mode does not exist on the iphone' if device_family_iphone?
|
463
|
+
|
464
|
+
if uia_available?
|
465
|
+
raise "UIA is available, use '_point_for_keyboard_mode_key' instead"
|
466
|
+
end
|
467
|
+
|
468
|
+
valid = [:top, :bottom]
|
469
|
+
unless valid.include? top_or_bottom
|
470
|
+
raise "expected '#{top_or_bottom}' to be one of '#{valid}'"
|
471
|
+
end
|
472
|
+
|
473
|
+
valid = [:split, :undocked, :docked]
|
474
|
+
unless valid.include? mode
|
475
|
+
raise "expected '#{mode}' to be one of '#{valid}'"
|
476
|
+
end
|
477
|
+
|
478
|
+
hash = {:split => {:top => 'Merge',
|
479
|
+
:bottom => 'Dock and Merge'},
|
480
|
+
:undocked => {:top => 'Dock',
|
481
|
+
:bottom => 'Split'},
|
482
|
+
:docked => {:top => 'Undock',
|
483
|
+
:bottom => 'Split'}}
|
484
|
+
mark = hash[mode][top_or_bottom]
|
485
|
+
"label marked:'#{mark}'"
|
486
|
+
end
|
487
|
+
|
488
|
+
# returns a query for touching the iPad keyboard +mode+ key.
|
489
|
+
#
|
490
|
+
# the +mode+ key is also know as the <tt>Hide keyboard</tt> key.
|
491
|
+
#
|
492
|
+
# use <tt>_point_for_keyboard_mode_key</tt> if there is a <tt>run_loop</tt>
|
493
|
+
# available
|
494
|
+
#
|
495
|
+
# raises an error when
|
496
|
+
# * the device is not an iPad
|
497
|
+
# * the app was launched with Instruments i.e. there is a <tt>run_loop</tt>
|
498
|
+
def _query_for_keyboard_mode_key
|
499
|
+
raise 'cannot detect keyboard mode key on iphone' if device_family_iphone?
|
500
|
+
if uia_available?
|
501
|
+
raise "UIA is available, use '_point_for_keyboard_mode_key' instead"
|
502
|
+
end
|
503
|
+
qstr = "view:'UIKBKeyView'"
|
504
|
+
idx = query(qstr).count - 1
|
505
|
+
"#{qstr} index:#{idx}"
|
506
|
+
end
|
507
|
+
|
508
|
+
# touches the bottom option on the popup dialog that is presented when the
|
509
|
+
# the iPad keyboard +mode+ key is touched and held.
|
510
|
+
#
|
511
|
+
# the +mode+ key is also know as the <tt>Hide keyboard</tt> key.
|
512
|
+
#
|
513
|
+
# the +mode+ key allows the user to undock, dock, or split the keyboard.
|
514
|
+
def _touch_bottom_keyboard_mode_row
|
515
|
+
mode = ipad_keyboard_mode
|
516
|
+
if uia_available?
|
517
|
+
start_pt = _point_for_ipad_keyboard_mode_key
|
518
|
+
# there are 10 pt btw the key and the popup and the row is 50 pt
|
519
|
+
y_offset = 10 + 25
|
520
|
+
end_pt = {:x => (start_pt[:x] - 40), :y => (start_pt[:y] - y_offset)}
|
521
|
+
uia_pan_offset(start_pt, end_pt, {})
|
522
|
+
else
|
523
|
+
pan(_query_for_keyboard_mode_key, nil, {:duration => 1.0})
|
524
|
+
touch(_query_for_touch_for_keyboard_mode_option(:bottom, mode))
|
525
|
+
sleep(0.5)
|
526
|
+
end
|
527
|
+
2.times { sleep(0.5) }
|
528
|
+
end
|
529
|
+
|
530
|
+
# touches the top option on the popup dialog that is presented when the
|
531
|
+
# the iPad keyboard +mode+ key is touched and held.
|
532
|
+
#
|
533
|
+
# the +mode+ key is also know as the <tt>Hide keyboard</tt> key.
|
534
|
+
#
|
535
|
+
# the +mode+ key allows the user to undock, dock, or split the keyboard.
|
536
|
+
def _touch_top_keyboard_mode_row
|
537
|
+
mode = ipad_keyboard_mode
|
538
|
+
if uia_available?
|
539
|
+
start_pt = _point_for_ipad_keyboard_mode_key
|
540
|
+
# there are 10 pt btw the key and the popup and each row is 50 pt
|
541
|
+
# NB: no amount of offsetting seems to allow touching the top row
|
542
|
+
# when the keyboard is split
|
543
|
+
y_offset = 10 + 50 + 25
|
544
|
+
end_pt = {:x => (start_pt[:x] - 40), :y => (start_pt[:y] - y_offset)}
|
545
|
+
uia_pan_offset(start_pt, end_pt, {:duration => 1.0})
|
546
|
+
else
|
547
|
+
pan(_query_for_keyboard_mode_key, nil, {})
|
548
|
+
touch(_query_for_touch_for_keyboard_mode_option(:top, mode))
|
549
|
+
sleep(0.5)
|
550
|
+
end
|
551
|
+
2.times { sleep(0.5) }
|
552
|
+
end
|
553
|
+
|
554
|
+
# ensures that the iPad keyboard is +docked+
|
555
|
+
#
|
556
|
+
# +docked+ means the keyboard is pinned to bottom of the view
|
557
|
+
#
|
558
|
+
# if the device is not an iPad, this is behaves like a call to
|
559
|
+
# <tt>wait_for_keyboard</tt>
|
560
|
+
#
|
561
|
+
# raises an error when
|
562
|
+
# * there is no visible keyboard or
|
563
|
+
# * the +docked+ keyboard cannot be achieved
|
564
|
+
def ensure_docked_keyboard
|
565
|
+
wait_for_keyboard
|
566
|
+
|
567
|
+
return if device_family_iphone?
|
568
|
+
|
569
|
+
mode = ipad_keyboard_mode
|
570
|
+
case mode
|
571
|
+
when :split then
|
572
|
+
_touch_bottom_keyboard_mode_row
|
573
|
+
when :undocked then
|
574
|
+
_touch_top_keyboard_mode_row
|
575
|
+
when :docked then
|
576
|
+
# already docked
|
577
|
+
else
|
578
|
+
screenshot_and_raise "expected '#{mode}' to be one of #{_ipad_keyboard_modes}"
|
579
|
+
end
|
580
|
+
|
581
|
+
begin
|
582
|
+
wait_for({:post_timeout => 1.0}) do
|
583
|
+
docked_keyboard_visible?
|
150
584
|
end
|
585
|
+
rescue
|
586
|
+
mode = ipad_keyboard_mode
|
587
|
+
o = status_bar_orientation
|
588
|
+
screenshot_and_raise "expected keyboard to be ':docked' but found '#{mode}' in orientation '#{o}'"
|
151
589
|
end
|
590
|
+
end
|
591
|
+
|
592
|
+
|
593
|
+
# ensures that the iPad keyboard is +undocked+
|
594
|
+
#
|
595
|
+
# +undocked+ means the keyboard is floating in the middle of the view
|
596
|
+
#
|
597
|
+
# if the device is not an iPad, this is behaves like a call to
|
598
|
+
# <tt>wait_for_keyboard</tt>
|
599
|
+
#
|
600
|
+
# raises an error when
|
601
|
+
# * there is no visible keyboard or
|
602
|
+
# * the an +undocked+ keyboard cannot be achieved
|
603
|
+
def ensure_undocked_keyboard
|
604
|
+
wait_for_keyboard()
|
152
605
|
|
606
|
+
return if device_family_iphone?
|
607
|
+
|
608
|
+
mode = ipad_keyboard_mode
|
609
|
+
case mode
|
610
|
+
when :split then
|
611
|
+
# keep these condition separate because even though they do the same
|
612
|
+
# thing, the else condition is a hack
|
613
|
+
if ios5?
|
614
|
+
# iOS 5 has no 'Merge' feature in split keyboard, so dock first then
|
615
|
+
# undock from docked mode
|
616
|
+
_touch_bottom_keyboard_mode_row
|
617
|
+
_wait_for_keyboard_in_mode(:docked)
|
618
|
+
else
|
619
|
+
# in iOS > 5, it seems to be impossible consistently touch the
|
620
|
+
# the top keyboard mode popup button, so we punt
|
621
|
+
_touch_bottom_keyboard_mode_row
|
622
|
+
_wait_for_keyboard_in_mode(:docked)
|
623
|
+
end
|
624
|
+
_touch_top_keyboard_mode_row
|
625
|
+
when :undocked then
|
626
|
+
# already undocked
|
627
|
+
when :docked then
|
628
|
+
_touch_top_keyboard_mode_row
|
629
|
+
else
|
630
|
+
screenshot_and_raise "expected '#{mode}' to be one of #{_ipad_keyboard_modes}"
|
631
|
+
end
|
153
632
|
|
633
|
+
_wait_for_keyboard_in_mode(:undocked)
|
154
634
|
end
|
155
635
|
|
156
636
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
637
|
+
# ensures that the iPad keyboard is +split+
|
638
|
+
#
|
639
|
+
# +split+ means the keyboard is floating in the middle of the view and is
|
640
|
+
# split into two sections to enable faster thumb typing.
|
641
|
+
#
|
642
|
+
# if the device is not an iPad, this is behaves like a call to
|
643
|
+
# <tt>wait_for_keyboard</tt>
|
644
|
+
#
|
645
|
+
# raises an error when
|
646
|
+
# * there is no visible keyboard or
|
647
|
+
# * the an +undocked+ keyboard cannot be achieved
|
648
|
+
def ensure_split_keyboard
|
649
|
+
wait_for_keyboard
|
650
|
+
|
651
|
+
return if device_family_iphone?
|
652
|
+
|
653
|
+
mode = ipad_keyboard_mode
|
654
|
+
case mode
|
655
|
+
when :split then
|
656
|
+
# already split
|
657
|
+
when :undocked then
|
658
|
+
_touch_bottom_keyboard_mode_row
|
659
|
+
when :docked then
|
660
|
+
_touch_bottom_keyboard_mode_row
|
661
|
+
else
|
662
|
+
screenshot_and_raise "expected '#{mode}' to be one of #{_ipad_keyboard_modes}"
|
663
|
+
end
|
664
|
+
|
665
|
+
_wait_for_keyboard_in_mode(:split)
|
666
|
+
end
|
667
|
+
|
668
|
+
def _wait_for_keyboard_in_mode(mode, opts={})
|
669
|
+
default_opts = {:post_timeout => 1.0}
|
670
|
+
opts = default_opts.merge(opts)
|
671
|
+
begin
|
672
|
+
wait_for(opts) do
|
673
|
+
case mode
|
674
|
+
when :split then
|
675
|
+
split_keyboard_visible?
|
676
|
+
when :undocked
|
677
|
+
undocked_keyboard_visible?
|
678
|
+
when :docked
|
679
|
+
docked_keyboard_visible?
|
680
|
+
else
|
681
|
+
screenshot_and_raise "expected '#{mode}' to be one of #{_ipad_keyboard_modes}"
|
682
|
+
end
|
683
|
+
end
|
684
|
+
rescue
|
685
|
+
actual = ipad_keyboard_mode
|
686
|
+
o = status_bar_orientation
|
687
|
+
screenshot_and_raise "expected keyboard to be '#{mode}' but found '#{actual}' in orientation '#{o}'"
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
# used for detecting keyboards that are not normally visible to calabash
|
692
|
+
# e.g. the keyboard on +'z'+
|
693
|
+
#
|
694
|
+
# IMPORTANT this should only be used when the app does not respond to
|
695
|
+
# <tt>keyboard_visible?</tt>
|
696
|
+
#
|
697
|
+
# raises an error if the there is no <tt>run_loop</tt>
|
698
|
+
def uia_keyboard_visible?
|
699
|
+
unless uia_available?
|
700
|
+
screenshot_and_raise 'only available if there is a run_loop i.e. the app was launched with Instruments'
|
701
|
+
end
|
702
|
+
# TODO refactor keyboard detection to use uia() function conventions (instead of UIATarget...)
|
703
|
+
res = uia('UIATarget.localTarget().frontMostApp().keyboard()')['value']
|
704
|
+
not res.eql?(':nil')
|
705
|
+
end
|
706
|
+
|
707
|
+
# waits for a keyboard that is not normally visible to calabash
|
708
|
+
# e.g. the keyboard on +MFMailComposeViewController+
|
709
|
+
#
|
710
|
+
# IMPORTANT this should only be used when the app does not respond to
|
711
|
+
# <tt>keyboard_visible?</tt>
|
712
|
+
#
|
713
|
+
# raises an error if the there is no <tt>run_loop</tt>
|
714
|
+
def uia_wait_for_keyboard(opts={})
|
715
|
+
unless uia_available?
|
716
|
+
screenshot_and_raise 'only available if there is a run_loop i.e. the app was launched with Instruments'
|
717
|
+
end
|
718
|
+
default_opts = {:timeout => 10,
|
719
|
+
:retry_frequency => 0.1,
|
720
|
+
:post_timeout => 0.5}
|
721
|
+
opts = default_opts.merge(opts)
|
722
|
+
unless opts[:timeout_message]
|
723
|
+
msg = "waited for '#{opts[:timeout]}' for keyboard"
|
724
|
+
opts[:timeout_message] = msg
|
725
|
+
end
|
726
|
+
|
727
|
+
wait_for(opts) do
|
728
|
+
uia_keyboard_visible?
|
168
729
|
end
|
169
730
|
end
|
170
731
|
|