calabash-cucumber 0.9.74 → 0.9.80.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- calabash-cucumber (0.9.74)
4
+ calabash-cucumber (0.9.80.pre.1)
5
5
  CFPropertyList
6
6
  cucumber
7
7
  json
8
- net-http-persistent
9
8
  sim_launcher (= 0.3.8)
10
9
  slowhandcuke
11
10
 
@@ -23,7 +22,6 @@ GEM
23
22
  gherkin (2.11.1)
24
23
  json (>= 1.4.6)
25
24
  json (1.7.3)
26
- net-http-persistent (2.7)
27
25
  rack (1.4.1)
28
26
  rack-protection (1.2.0)
29
27
  rack
@@ -71,8 +71,9 @@ def run(options={:build_dir=>"Calabash",
71
71
 
72
72
  if ENV['NO_BUILD'] != "1"
73
73
  if !build(options)
74
- msg("Info") do
74
+ msg("Error") do
75
75
  puts "Build failed. Please consult logs. Aborting."
76
+ exit(false)
76
77
  end
77
78
  end
78
79
  end
@@ -80,12 +81,19 @@ def run(options={:build_dir=>"Calabash",
80
81
  if ENV["NO_GEN"] != "1"
81
82
  if !File.directory?("features")
82
83
  calabash_scaffold
84
+ else
85
+ msg("Info") do
86
+ puts "Detected features folder, will not generate..."
87
+ end
83
88
  end
84
89
  end
85
90
 
86
91
  default_path = "#{options[:dstroot]}/#{options[:wrapper_name]}"
87
92
  cmd = %Q[APP_BUNDLE_PATH="#{ENV['APP_BUNDLE_PATH'] || default_path}" cucumber]
88
- puts cmd
93
+ msg("Info") do
94
+ puts "Running command:"
95
+ puts cmd
96
+ end
89
97
  system(cmd)
90
98
  puts "Done..."
91
99
  end
@@ -17,6 +17,8 @@ def print_usage
17
17
  prints more detailed help information.
18
18
  gen
19
19
  generate a features folder structure.
20
+ console
21
+ starts an interactive console to interact with your app via Calabash
20
22
  setup (EXPERIMENTAL) [opt path]?
21
23
  setup your XCode project for calabash-ios
22
24
  download [opt path]?
@@ -35,6 +37,7 @@ def print_usage
35
37
  change the default iOS Simulator device.
36
38
  EOF
37
39
  end
40
+
38
41
  def print_help
39
42
  file = File.join(File.dirname(__FILE__), '..', 'doc', 'calabash-ios-help.txt')
40
43
  system("less #{file}")
@@ -21,6 +21,5 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency( "CFPropertyList" )
22
22
  s.add_dependency( "sim_launcher", "0.3.8") #Recommended by Pete for now
23
23
  s.add_dependency( "slowhandcuke" )
24
- s.add_dependency( "net-http-persistent" )
25
24
 
26
25
  end
@@ -14,7 +14,10 @@ Usage: calabash-ios <command-name> [parameters]
14
14
  gen creates a skeleton features dir. This is usually used once when
15
15
  setting up calabash to ensure that the features folder contains
16
16
  the right step definitions and environment to run with cucumber.
17
-
17
+ console
18
+ starts an interactive console to interact with your app via Calabash.
19
+ Copies the irb_ios5.sh and irb_ios4.sh scripts and the .irbrc file
20
+ then launches irb_ios5.sh.
18
21
  setup [path]? (EXPERIMENTAL) Automates setting up your iOS Xcode project
19
22
  with calabash-ios-server. It is your responsibility to ensure
20
23
  that your production build does not link with calabash.framework.
@@ -34,7 +34,7 @@ Then /^I (?:press|touch) the "([^\"]*)" button$/ do |name|
34
34
  end
35
35
 
36
36
  Then /^I (?:press|touch) the "([^\"]*)" (?:input|text) field$/ do |name|
37
- touch("textField placeholder:'#{name}'")
37
+ touch("textField placeholdwer:'#{name}'")
38
38
  sleep(STEP_PAUSE)
39
39
  end
40
40
 
@@ -1,2 +1,6 @@
1
+ require 'calabash-cucumber/core'
2
+ require 'calabash-cucumber/tests_helpers'
3
+ require 'calabash-cucumber/keyboard_helpers'
4
+ require 'calabash-cucumber/wait_helpers'
1
5
  require 'calabash-cucumber/operations'
2
- require 'calabash-cucumber/version'
6
+ require 'calabash-cucumber/version'
@@ -0,0 +1,310 @@
1
+ module Calabash
2
+ module Cucumber
3
+ module Core
4
+
5
+ DATA_PATH = File.expand_path(File.dirname(__FILE__))
6
+
7
+ def macro(txt)
8
+ if self.respond_to? :step
9
+ step(txt)
10
+ else
11
+ Then txt
12
+ end
13
+ end
14
+
15
+ def query(uiquery, *args)
16
+ map(uiquery, :query, *args)
17
+ end
18
+
19
+ def touch(uiquery, options={})
20
+ options[:query] = uiquery
21
+ views_touched = playback("touch", options)
22
+ unless uiquery.nil?
23
+ screenshot_and_raise "could not find view to touch: '#{uiquery}', args: #{options}" if views_touched.empty?
24
+ end
25
+ views_touched
26
+ end
27
+
28
+ def swipe(dir, options={})
29
+ dir = dir.to_sym
30
+ @current_rotation = @current_rotation || :down
31
+ if @current_rotation == :left
32
+ case dir
33
+ when :left then
34
+ dir = :down
35
+ when :right then
36
+ dir = :up
37
+ when :up then
38
+ dir = :left
39
+ when :down then
40
+ dir = :right
41
+ else
42
+ end
43
+ end
44
+ if @current_rotation == :right
45
+ case dir
46
+ when :left then
47
+ dir = :up
48
+ when :right then
49
+ dir = :down
50
+ when :up then
51
+ dir = :right
52
+ when :down then
53
+ dir = :left
54
+ else
55
+ end
56
+ end
57
+ if @current_rotation == :up
58
+ case dir
59
+ when :left then
60
+ dir = :right
61
+ when :right then
62
+ dir = :left
63
+ when :up then
64
+ dir = :down
65
+ when :down then
66
+ dir = :up
67
+ else
68
+ end
69
+ end
70
+ playback("swipe_#{dir}", options)
71
+ end
72
+
73
+ def cell_swipe(options={})
74
+ playback("cell_swipe", options)
75
+ end
76
+
77
+ def scroll(uiquery, direction)
78
+ views_touched=map(uiquery, :scroll, direction)
79
+ screenshot_and_raise "could not find view to scroll: '#{uiquery}', args: #{direction}" if views_touched.empty?
80
+ views_touched
81
+ end
82
+
83
+ def scroll_to_row(uiquery, number)
84
+ views_touched=map(uiquery, :scrollToRow, number)
85
+ if views_touched.empty? or views_touched.member? "<VOID>"
86
+ screenshot_and_raise "Unable to scroll: '#{uiquery}' to: #{number}"
87
+ end
88
+ views_touched
89
+ end
90
+
91
+ def pinch(in_out, options={})
92
+ file = "pinch_in"
93
+ if in_out.to_sym==:out
94
+ file = "pinch_out"
95
+ end
96
+ playback(file, options)
97
+ end
98
+
99
+ def rotate(dir)
100
+ @current_rotation = @current_rotation || :down
101
+ rotate_cmd = nil
102
+ case dir
103
+ when :left then
104
+ if @current_rotation == :down
105
+ rotate_cmd = "left_home_down"
106
+ @current_rotation = :right
107
+ elsif @current_rotation == :right
108
+ rotate_cmd = "left_home_right"
109
+ @current_rotation = :up
110
+ elsif @current_rotation == :left
111
+ rotate_cmd = "left_home_left"
112
+ @current_rotation = :down
113
+ elsif @current_rotation == :up
114
+ rotate_cmd = "left_home_up"
115
+ @current_rotation = :left
116
+ end
117
+ when :right then
118
+ if @current_rotation == :down
119
+ rotate_cmd = "right_home_down"
120
+ @current_rotation = :left
121
+ elsif @current_rotation == :left
122
+ rotate_cmd = "right_home_left"
123
+ @current_rotation = :up
124
+ elsif @current_rotation == :right
125
+ rotate_cmd = "right_home_right"
126
+ @current_rotation = :down
127
+ elsif @current_rotation == :up
128
+ rotate_cmd = "right_home_up"
129
+ @current_rotation = :right
130
+ end
131
+ end
132
+
133
+ if rotate_cmd.nil?
134
+ screenshot_and_raise "Does not support rotating #{dir} when home button is pointing #{@current_rotation}"
135
+ end
136
+ playback("rotate_#{rotate_cmd}")
137
+ end
138
+
139
+ def background(secs)
140
+ http({:method => :post, :path => 'background'}, {:duration => secs})
141
+ end
142
+
143
+
144
+ def load_playback_data(recording, options={})
145
+ os = options["OS"] || ENV["OS"] || "ios5"
146
+ device = options["DEVICE"] || ENV["DEVICE"] || "iphone"
147
+
148
+ rec_dir = ENV['PLAYBACK_DIR'] || "#{Dir.pwd}/playback"
149
+ if !recording.end_with? ".base64"
150
+ recording = "#{recording}_#{os}_#{device}.base64"
151
+ end
152
+ data = nil
153
+ if (File.exists?(recording))
154
+ data = File.read(recording)
155
+ elsif (File.exists?("features/#{recording}"))
156
+ data = File.read("features/#{recording}")
157
+ elsif (File.exists?("#{rec_dir}/#{recording}"))
158
+ data = File.read("#{rec_dir}/#{recording}")
159
+ elsif (File.exists?("#{DATA_PATH}/resources/#{recording}"))
160
+ data = File.read("#{DATA_PATH}/resources/#{recording}")
161
+ else
162
+ screenshot_and_raise "Playback not found: #{recording} (searched for #{recording} in #{Dir.pwd}, #{rec_dir}, #{DATA_PATH}/resources"
163
+ end
164
+ data
165
+ end
166
+
167
+ def playback(recording, options={})
168
+ data = load_playback_data(recording)
169
+
170
+ post_data = %Q|{"events":"#{data}"|
171
+ post_data<< %Q|,"query":"#{escape_quotes(options[:query])}"| if options[:query]
172
+ post_data<< %Q|,"offset":#{options[:offset].to_json}| if options[:offset]
173
+ post_data<< %Q|,"reverse":#{options[:reverse]}| if options[:reverse]
174
+ post_data<< %Q|,"prototype":"#{options[:prototype]}"| if options[:prototype]
175
+ post_data << "}"
176
+
177
+ res = http({:method => :post, :raw => true, :path => 'play'}, post_data)
178
+
179
+ res = JSON.parse(res)
180
+ if res['outcome'] != 'SUCCESS'
181
+ screenshot_and_raise "playback failed because: #{res['reason']}\n#{res['details']}"
182
+ end
183
+ res['results']
184
+ end
185
+
186
+ def interpolate(recording, options={})
187
+ data = load_playback_data(recording)
188
+
189
+ post_data = %Q|{"events":"#{data}"|
190
+ post_data<< %Q|,"start":"#{escape_quotes(options[:start])}"| if options[:start]
191
+ post_data<< %Q|,"end":"#{escape_quotes(options[:end])}"| if options[:end]
192
+ post_data<< %Q|,"offset_start":#{options[:offset_start].to_json}| if options[:offset_start]
193
+ post_data<< %Q|,"offset_end":#{options[:offset_end].to_json}| if options[:offset_end]
194
+ post_data << "}"
195
+
196
+ res = http({:method => :post, :raw => true, :path => 'interpolate'}, post_data)
197
+
198
+ res = JSON.parse(res)
199
+ if res['outcome'] != 'SUCCESS'
200
+ screenshot_and_raise "interpolate failed because: #{res['reason']}\n#{res['details']}"
201
+ end
202
+ res['results']
203
+ end
204
+
205
+ def record_begin
206
+ http({:method => :post, :path => 'record'}, {:action => :start})
207
+ end
208
+
209
+ def record_end(file_name)
210
+ res = http({:method => :post, :path => 'record'}, {:action => :stop})
211
+ File.open("_recording.plist", 'wb') do |f|
212
+ f.write res
213
+ end
214
+ device = ENV['DEVICE'] || 'iphone'
215
+ os = ENV['OS'] || 'ios5'
216
+
217
+ file_name = "#{file_name}_#{os}_#{device}.base64"
218
+ system("/usr/bin/plutil -convert binary1 -o _recording_binary.plist _recording.plist")
219
+ system("openssl base64 -in _recording_binary.plist -out #{file_name}")
220
+ system("rm _recording.plist _recording_binary.plist")
221
+ file_name
222
+ end
223
+
224
+ def backdoor(sel, arg)
225
+ json = {
226
+ :selector => sel,
227
+ :arg => arg
228
+ }
229
+ res = http({:method => :post, :path => 'backdoor'}, json)
230
+ res = JSON.parse(res)
231
+ if res['outcome'] != 'SUCCESS'
232
+ screenshot_and_raise "backdoor #{json} failed because: #{res['reason']}\n#{res['details']}"
233
+ end
234
+ res['result']
235
+ end
236
+
237
+
238
+ def map(query, method_name, *method_args)
239
+ operation_map = {
240
+ :method_name => method_name,
241
+ :arguments => method_args
242
+ }
243
+ res = http({:method => :post, :path => 'map'},
244
+ {:query => query, :operation => operation_map})
245
+ res = JSON.parse(res)
246
+ if res['outcome'] != 'SUCCESS'
247
+ screenshot_and_raise "map #{query}, #{method_name} failed because: #{res['reason']}\n#{res['details']}"
248
+ end
249
+
250
+ res['results']
251
+ end
252
+
253
+ def http(options, data=nil)
254
+ url = url_for(options[:path])
255
+ if options[:method] == :post
256
+ req = Net::HTTP::Post.new url.path
257
+ if options[:raw]
258
+ req.body=data
259
+ else
260
+ req.body = data.to_json
261
+ end
262
+
263
+ else
264
+ req = Net::HTTP::Get.new url.path
265
+ end
266
+ make_http_request(url, req)
267
+ end
268
+
269
+
270
+ def url_for(verb)
271
+ url = URI.parse(ENV['DEVICE_ENDPOINT']|| "http://localhost:37265/")
272
+ url.path = '/'+verb
273
+ url
274
+ end
275
+
276
+ CAL_HTTP_RETRY_COUNT=3
277
+
278
+ def make_http_request(url, req)
279
+ body = nil
280
+ CAL_HTTP_RETRY_COUNT.times do |count|
281
+ begin
282
+ if not (@http) or not (@http.started?)
283
+ @http = init_request(url)
284
+ @http.start
285
+ end
286
+ body = @http.request(req).body
287
+ break
288
+ rescue Exception => e
289
+ if count < CAL_HTTP_RETRY_COUNT-1
290
+ puts "Retrying.."
291
+ else
292
+ puts "Failing..."
293
+ raise e
294
+ end
295
+ end
296
+ end
297
+
298
+ body
299
+ end
300
+
301
+ def init_request(url)
302
+ http = Net::HTTP.new(url.host, url.port)
303
+ if http.respond_to? :open_timeout=
304
+ http.open_timeout==15
305
+ end
306
+ http
307
+ end
308
+ end
309
+ end
310
+ end
@@ -1,7 +1,6 @@
1
- require 'calabash-cucumber/color_helper'
1
+ require 'calabash-cucumber/wait_helpers'
2
2
  require 'calabash-cucumber/operations'
3
3
 
4
- World(Calabash::Cucumber::ColorHelper)
5
4
  World(Calabash::Cucumber::Operations)
6
5
 
7
6
  AfterConfiguration do
@@ -0,0 +1,112 @@
1
+ require 'calabash-cucumber/tests_helpers'
2
+
3
+ module Calabash
4
+ module Cucumber
5
+ module KeyboardHelpers
6
+ include Calabash::Cucumber::TestsHelpers
7
+
8
+ KEYPLANE_NAMES = {
9
+ :small_letters => "small-letters",
10
+ :capital_letters => "capital-letters",
11
+ :numbers_and_punctuation => "numbers-and-punctuation",
12
+ :first_alternate => "first-alternate",
13
+ :numbers_and_punctuation_alternate => "numbers-and-punctuation-alternate"
14
+ }
15
+
16
+ #Possible values
17
+ # 'Dictation'
18
+ # 'Shift'
19
+ # 'Delete'
20
+ # 'International'
21
+ # 'More'
22
+ # 'Return'
23
+ def keyboard_enter_char(chr, should_screenshot=true)
24
+ #map(nil, :keyboard, load_playback_data("touch_done"), chr)
25
+ res = http({:method => :post, :path => 'keyboard'},
26
+ {:key => chr, :events => load_playback_data("touch_done")})
27
+ res = JSON.parse(res)
28
+ if res['outcome'] != 'SUCCESS'
29
+ msg = "Keyboard enter failed failed because: #{res['reason']}\n#{res['details']}"
30
+ if should_screenshot
31
+ screenshot_and_raise msg
32
+ else
33
+ raise msg
34
+ end
35
+ end
36
+ res['results']
37
+ end
38
+
39
+ def done
40
+ keyboard_enter_char "Return"
41
+ end
42
+
43
+
44
+ def current_keyplane
45
+ kp_arr = _do_keyplane(
46
+ lambda { query("view:'UIKBKeyplaneView'", "keyplane", "componentName") },
47
+ lambda { query("view:'UIKBKeyplaneView'", "keyplane", "name") })
48
+ kp_arr.first.downcase
49
+ end
50
+
51
+ def search_keyplanes_and_enter_char(chr, visited=Set.new)
52
+ cur_kp = current_keyplane
53
+ begin
54
+ keyboard_enter_char(chr, false)
55
+ return true #found
56
+ rescue
57
+ visited.add(cur_kp)
58
+
59
+ #figure out keyplane alternates
60
+ props = _do_keyplane(
61
+ lambda { query("view:'UIKBKeyplaneView'", "keyplane", "properties") },
62
+ lambda { query("view:'UIKBKeyplaneView'", "keyplane", "attributes", "dict") }
63
+ ).first
64
+
65
+ known = KEYPLANE_NAMES.values
66
+
67
+ found = false
68
+ ["shift", "more"].each do |key|
69
+ plane = props["#{key}-alternate"]
70
+ if (known.member?(plane) and
71
+ not visited.member?(plane))
72
+ keyboard_enter_char(key.capitalize, false)
73
+ found = search_keyplanes_and_enter_char(chr, visited)
74
+ return true if found
75
+ #not found => go back
76
+ keyboard_enter_char(key.capitalize, false)
77
+ end
78
+ end
79
+ return false
80
+ end
81
+ end
82
+
83
+ def keyboard_enter_text(text)
84
+ fail("No visible keyboard") if element_does_not_exist("view:'UIKBKeyplaneView'")
85
+
86
+ text.each_char do |ch|
87
+ begin
88
+ keyboard_enter_char(ch, false)
89
+ rescue
90
+ search_keyplanes_and_enter_char(ch)
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+ def _do_keyplane(kbtree_proc, keyplane_proc)
97
+ desc = query("view:'UIKBKeyplaneView'", "keyplane")
98
+ fail("No keyplane (UIKBKeyplaneView keyplane)") if desc.empty?
99
+ fail("Several keyplanes (UIKBKeyplaneView keyplane)") if desc.count > 1
100
+ kp_desc = desc.first
101
+ if /^<UIKBTree/.match(kp_desc)
102
+ #ios5+
103
+ kbtree_proc.call
104
+ elsif /^<UIKBKeyplane/.match(kp_desc)
105
+ #ios4
106
+ keyplane_proc.call
107
+ end
108
+ end
109
+
110
+ end
111
+ end
112
+ end