iCuke 0.5.5 → 0.6.0

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.
Files changed (201) hide show
  1. data/.gitignore +2 -0
  2. data/.gitmodules +3 -0
  3. data/README.rdoc +8 -7
  4. data/Rakefile +10 -30
  5. data/VERSION +1 -1
  6. data/app/.gitignore +3 -0
  7. data/app/LICENSE +20 -0
  8. data/app/Readme.markdown +3 -0
  9. data/app/Shared/AppDelegate_Shared.h +17 -0
  10. data/app/Shared/AppDelegate_Shared.m +25 -0
  11. data/app/{images/yellowslide.png → Shared/Icon.png} +0 -0
  12. data/app/Shared/ModalViewController.h +15 -0
  13. data/app/Shared/ModalViewController.m +96 -0
  14. data/app/Shared/TestViewController_Shared.h +15 -0
  15. data/app/Shared/TestViewController_Shared.m +43 -0
  16. data/app/{Info.plist → Shared/Universal-Info.plist} +17 -4
  17. data/app/Shared/Universal_Prefix.pch +8 -0
  18. data/app/Shared/main.m +14 -0
  19. data/app/Universal.xcodeproj/project.pbxproj +301 -0
  20. data/app/iPad/AppDelegate_Pad.h +15 -0
  21. data/app/iPad/AppDelegate_Pad.m +28 -0
  22. data/app/iPad/Default-Landscape.png +0 -0
  23. data/app/iPad/Default-Portrait.png +0 -0
  24. data/app/iPad/MainWindow_Pad.xib +343 -0
  25. data/app/iPad/TestViewController_Pad.h +15 -0
  26. data/app/iPad/TestViewController_Pad.m +52 -0
  27. data/app/iPhone/AppDelegate_Phone.h +15 -0
  28. data/app/iPhone/AppDelegate_Phone.m +28 -0
  29. data/app/{images/segment_search.png → iPhone/Default.png} +0 -0
  30. data/app/iPhone/MainWindow_Phone.xib +363 -0
  31. data/app/iPhone/TestViewController_Phone.h +15 -0
  32. data/app/iPhone/TestViewController_Phone.m +36 -0
  33. data/bin/.gitignore +1 -0
  34. data/bin/icuke +10 -0
  35. data/bin/icuke-module +17 -0
  36. data/ext/.gitignore +1 -0
  37. data/ext/Rakefile +6 -0
  38. data/ext/WaxSim/Simulator.h +31 -0
  39. data/ext/WaxSim/Simulator.m +131 -0
  40. data/ext/WaxSim/WaxSim.m +110 -0
  41. data/ext/WaxSim/WaxSim.xcodeproj/project.pbxproj +238 -0
  42. data/ext/WaxSim/WaxSim_Prefix.pch +7 -0
  43. data/ext/WaxSim/iPhoneSimulatorRemoteClient/iPhoneSimulatorRemoteClient.h +126 -0
  44. data/ext/iCuke/Rakefile +14 -17
  45. data/ext/iCuke/libicuke-sdk3.1.dylib +0 -0
  46. data/ext/iCuke/libicuke-sdk4.0.dylib +0 -0
  47. data/ext/iCuke/libicuke.dylib +0 -0
  48. data/ext/iCuke/{DefaultsResponse.h → sdk3.1/DefaultsResponse.h} +0 -0
  49. data/ext/iCuke/{DefaultsResponse.m → sdk3.1/DefaultsResponse.m} +0 -0
  50. data/ext/iCuke/{EventResponse.h → sdk3.1/EventResponse.h} +0 -0
  51. data/ext/iCuke/{EventResponse.m → sdk3.1/EventResponse.m} +0 -0
  52. data/ext/iCuke/sdk3.1/ModuleResponse.h +5 -0
  53. data/ext/iCuke/sdk3.1/ModuleResponse.m +76 -0
  54. data/ext/iCuke/sdk3.1/Rakefile +20 -0
  55. data/ext/iCuke/{Recorder.h → sdk3.1/Recorder.h} +0 -0
  56. data/ext/iCuke/{Recorder.m → sdk3.1/Recorder.m} +0 -0
  57. data/ext/iCuke/{RecorderResponse.h → sdk3.1/RecorderResponse.h} +0 -0
  58. data/ext/iCuke/{RecorderResponse.m → sdk3.1/RecorderResponse.m} +0 -0
  59. data/ext/iCuke/{SynthesizeSingleton.h → sdk3.1/SynthesizeSingleton.h} +0 -0
  60. data/ext/iCuke/{ViewResponse.h → sdk3.1/ViewResponse.h} +0 -0
  61. data/ext/iCuke/{ViewResponse.m → sdk3.1/ViewResponse.m} +0 -0
  62. data/ext/iCuke/{Viewer.h → sdk3.1/Viewer.h} +0 -0
  63. data/ext/iCuke/sdk3.1/Viewer.m +203 -0
  64. data/ext/iCuke/{iCukeHTTPResponseHandler.h → sdk3.1/iCukeHTTPResponseHandler.h} +0 -0
  65. data/ext/iCuke/{iCukeHTTPResponseHandler.m → sdk3.1/iCukeHTTPResponseHandler.m} +0 -0
  66. data/ext/iCuke/{iCukeHTTPServer.h → sdk3.1/iCukeHTTPServer.h} +0 -0
  67. data/ext/iCuke/{iCukeHTTPServer.m → sdk3.1/iCukeHTTPServer.m} +0 -0
  68. data/ext/iCuke/{iCukeServer.h → sdk3.1/iCukeServer.h} +0 -2
  69. data/ext/iCuke/{iCukeServer.m → sdk3.1/iCukeServer.m} +22 -20
  70. data/ext/iCuke/{json → sdk3.1/json}/JSON.h +0 -0
  71. data/ext/iCuke/{json → sdk3.1/json}/NSObject+SBJSON.h +0 -0
  72. data/ext/iCuke/{json → sdk3.1/json}/NSObject+SBJSON.m +0 -0
  73. data/ext/iCuke/{json → sdk3.1/json}/NSString+SBJSON.h +0 -0
  74. data/ext/iCuke/{json → sdk3.1/json}/NSString+SBJSON.m +0 -0
  75. data/ext/iCuke/{json → sdk3.1/json}/SBJSON.h +0 -0
  76. data/ext/iCuke/{json → sdk3.1/json}/SBJSON.m +0 -0
  77. data/ext/iCuke/{json → sdk3.1/json}/SBJsonBase.h +0 -0
  78. data/ext/iCuke/{json → sdk3.1/json}/SBJsonBase.m +0 -0
  79. data/ext/iCuke/{json → sdk3.1/json}/SBJsonParser.h +0 -0
  80. data/ext/iCuke/{json → sdk3.1/json}/SBJsonParser.m +0 -0
  81. data/ext/iCuke/{json → sdk3.1/json}/SBJsonWriter.h +0 -0
  82. data/ext/iCuke/{json → sdk3.1/json}/SBJsonWriter.m +0 -0
  83. data/ext/iCuke/sdk4.0/DefaultsResponse.h +5 -0
  84. data/ext/iCuke/sdk4.0/DefaultsResponse.m +66 -0
  85. data/ext/iCuke/sdk4.0/EventResponse.h +5 -0
  86. data/ext/iCuke/sdk4.0/EventResponse.m +130 -0
  87. data/ext/iCuke/sdk4.0/ModuleResponse.h +5 -0
  88. data/ext/iCuke/sdk4.0/ModuleResponse.m +76 -0
  89. data/ext/iCuke/sdk4.0/Rakefile +20 -0
  90. data/ext/iCuke/sdk4.0/Recorder.h +17 -0
  91. data/ext/iCuke/sdk4.0/Recorder.m +90 -0
  92. data/ext/iCuke/sdk4.0/RecorderResponse.h +6 -0
  93. data/ext/iCuke/sdk4.0/RecorderResponse.m +68 -0
  94. data/ext/iCuke/sdk4.0/SynthesizeSingleton.h +68 -0
  95. data/ext/iCuke/sdk4.0/ViewResponse.h +5 -0
  96. data/ext/iCuke/sdk4.0/ViewResponse.m +84 -0
  97. data/ext/iCuke/sdk4.0/Viewer.h +8 -0
  98. data/ext/iCuke/{Viewer.m → sdk4.0/Viewer.m} +14 -0
  99. data/ext/iCuke/sdk4.0/iCukeHTTPResponseHandler.h +50 -0
  100. data/ext/iCuke/sdk4.0/iCukeHTTPResponseHandler.m +381 -0
  101. data/ext/iCuke/sdk4.0/iCukeHTTPServer.h +53 -0
  102. data/ext/iCuke/sdk4.0/iCukeHTTPServer.m +365 -0
  103. data/ext/iCuke/sdk4.0/iCukeServer.h +14 -0
  104. data/ext/iCuke/sdk4.0/iCukeServer.m +65 -0
  105. data/ext/iCuke/sdk4.0/json/JSON.h +50 -0
  106. data/ext/iCuke/sdk4.0/json/NSObject+SBJSON.h +68 -0
  107. data/ext/iCuke/sdk4.0/json/NSObject+SBJSON.m +53 -0
  108. data/ext/iCuke/sdk4.0/json/NSString+SBJSON.h +58 -0
  109. data/ext/iCuke/sdk4.0/json/NSString+SBJSON.m +55 -0
  110. data/ext/iCuke/sdk4.0/json/SBJSON.h +75 -0
  111. data/ext/iCuke/sdk4.0/json/SBJSON.m +212 -0
  112. data/ext/iCuke/sdk4.0/json/SBJsonBase.h +86 -0
  113. data/ext/iCuke/sdk4.0/json/SBJsonBase.m +78 -0
  114. data/ext/iCuke/sdk4.0/json/SBJsonParser.h +87 -0
  115. data/ext/iCuke/sdk4.0/json/SBJsonParser.m +475 -0
  116. data/ext/iCuke/sdk4.0/json/SBJsonWriter.h +129 -0
  117. data/ext/iCuke/sdk4.0/json/SBJsonWriter.m +228 -0
  118. data/features/iphone.feature +15 -0
  119. data/features/support/env.rb +1 -1
  120. data/iCuke.gemspec +118 -114
  121. data/lib/icuke/cucumber.rb +93 -64
  122. data/lib/icuke/screen.rb +110 -0
  123. data/lib/icuke/sdk.rb +134 -0
  124. data/lib/icuke/simulate.rb +9 -5
  125. data/lib/icuke/simulator.rb +10 -11
  126. data/lib/icuke/waxsim.rb +36 -0
  127. data/lib/icuke.rb +2 -1
  128. metadata +124 -129
  129. data/app/AlertsViewController.h +0 -59
  130. data/app/AlertsViewController.m +0 -341
  131. data/app/AppDelegate.h +0 -60
  132. data/app/AppDelegate.m +0 -101
  133. data/app/ButtonsViewController.h +0 -80
  134. data/app/ButtonsViewController.m +0 -448
  135. data/app/Constants.h +0 -58
  136. data/app/ControlsViewController.h +0 -72
  137. data/app/ControlsViewController.m +0 -379
  138. data/app/ImagesViewController.h +0 -62
  139. data/app/ImagesViewController.m +0 -137
  140. data/app/MainViewController.h +0 -57
  141. data/app/MainViewController.m +0 -262
  142. data/app/Picker/CustomPickerDataSource.h +0 -55
  143. data/app/Picker/CustomPickerDataSource.m +0 -135
  144. data/app/Picker/CustomView.h +0 -62
  145. data/app/Picker/CustomView.m +0 -119
  146. data/app/PickerViewController.h +0 -88
  147. data/app/PickerViewController.m +0 -402
  148. data/app/Prefix.pch +0 -5
  149. data/app/ReadMe.txt +0 -82
  150. data/app/SearchBarController.h +0 -58
  151. data/app/SearchBarController.m +0 -105
  152. data/app/SegmentViewController.h +0 -54
  153. data/app/SegmentViewController.m +0 -195
  154. data/app/TextFieldController.h +0 -67
  155. data/app/TextFieldController.m +0 -342
  156. data/app/TextViewController.h +0 -57
  157. data/app/TextViewController.m +0 -164
  158. data/app/ToolbarViewController.h +0 -74
  159. data/app/ToolbarViewController.m +0 -303
  160. data/app/TransitionViewController.h +0 -64
  161. data/app/TransitionViewController.m +0 -178
  162. data/app/UICatalog.xcodeproj/project.pbxproj +0 -615
  163. data/app/WebViewController.h +0 -57
  164. data/app/WebViewController.m +0 -173
  165. data/app/en.lproj/AlertsViewController.xib +0 -144
  166. data/app/en.lproj/ButtonsViewController.xib +0 -147
  167. data/app/en.lproj/ControlsViewController.xib +0 -147
  168. data/app/en.lproj/ImagesViewController.xib +0 -264
  169. data/app/en.lproj/Localizable.strings +0 -41
  170. data/app/en.lproj/MainWindow.xib +0 -306
  171. data/app/en.lproj/PickerViewController.xib +0 -415
  172. data/app/en.lproj/SearchBarController.xib +0 -142
  173. data/app/en.lproj/SegmentViewController.xib +0 -143
  174. data/app/en.lproj/TextFieldController.xib +0 -167
  175. data/app/en.lproj/TextViewController.xib +0 -149
  176. data/app/en.lproj/ToolbarViewController.xib +0 -491
  177. data/app/en.lproj/TransitionViewController.xib +0 -255
  178. data/app/en.lproj/WebViewController.xib +0 -141
  179. data/app/images/12-6AM.png +0 -0
  180. data/app/images/12-6PM.png +0 -0
  181. data/app/images/6-12AM.png +0 -0
  182. data/app/images/6-12PM.png +0 -0
  183. data/app/images/Default.png +0 -0
  184. data/app/images/Icon.png +0 -0
  185. data/app/images/UIButton_custom.png +0 -0
  186. data/app/images/blueButton.png +0 -0
  187. data/app/images/orangeslide.png +0 -0
  188. data/app/images/scene1.jpg +0 -0
  189. data/app/images/scene2.jpg +0 -0
  190. data/app/images/scene3.jpg +0 -0
  191. data/app/images/scene4.jpg +0 -0
  192. data/app/images/scene5.jpg +0 -0
  193. data/app/images/segment_check.png +0 -0
  194. data/app/images/segment_tools.png +0 -0
  195. data/app/images/slider_ball.png +0 -0
  196. data/app/images/whiteButton.png +0 -0
  197. data/app/main.m +0 -58
  198. data/features/uicatalog.feature +0 -20
  199. data/lib/icuke/com.apple.Accessibility.plist +0 -0
  200. data/lib/icuke/headless.rb +0 -54
  201. data/lib/icuke/xcode.rb +0 -164
@@ -1,12 +1,14 @@
1
1
  require 'nokogiri'
2
2
 
3
+ require 'icuke/sdk'
3
4
  require 'icuke/simulator'
4
5
  require 'icuke/simulate'
6
+ require 'icuke/screen'
5
7
 
6
8
  class ICukeWorld
7
9
  include ICuke::Simulate::Gestures
8
10
 
9
- attr_reader :response
11
+ attr_reader :response, :simulator
10
12
 
11
13
  def initialize
12
14
  @simulator = ICuke::Simulator.new
@@ -20,8 +22,8 @@ class ICukeWorld
20
22
  @simulator.quit
21
23
  end
22
24
 
23
- def page
24
- @xml ||= Nokogiri::XML::Document.parse(response).root
25
+ def screen
26
+ @screen ||= Screen.new(response)
25
27
  end
26
28
 
27
29
  def response
@@ -32,39 +34,13 @@ class ICukeWorld
32
34
  @simulator.record
33
35
  end
34
36
 
35
- def can_see?(text, scope = '')
36
- page.xpath(%Q{#{scope}//*[contains(., "#{text}") or contains(@label, "#{text}") or contains(@value, "#{text}")]}).any?
37
- end
38
-
39
- def onscreen?(x, y)
40
- return x >= 0 && y >= 0 && x < 320 && y < 480
41
- end
42
-
43
37
  def tap(label, options = {}, &block)
44
38
  options = {
45
39
  :pause => true
46
40
  }.merge(options)
47
41
 
48
- element =
49
- page.xpath(
50
- %Q{//*[#{trait(:button, :updates_frequently, :keyboard_key)} and @label="#{label}" and frame]},
51
- %Q{//*[#{trait(:link)} and @value="#{label}" and frame]},
52
- %Q{//*[@label="#{label}" and frame]}
53
- ).first
54
-
55
- raise %Q{No element labelled "#{label}" found in: #{page}} unless element
56
-
57
- # This seems brittle, revist how to fetch the frame without relying on it being the only child
58
- frame = element.child
59
-
60
- x = frame['x'].to_f
61
- y = frame['y'].to_f
62
-
63
- # Hit the element in the middle
64
- x += (frame['width'].to_f / 2)
65
- y += (frame['height'].to_f / 2)
66
-
67
- raise %Q{Element "#{label}" is off screen in: #{page}} unless onscreen?(x, y)
42
+ element = screen.first_tappable_element(label)
43
+ x, y = screen.element_center(element)
68
44
 
69
45
  @simulator.fire_event(Tap.new(x, y, options))
70
46
 
@@ -76,27 +52,47 @@ class ICukeWorld
76
52
  end
77
53
 
78
54
  def swipe(direction, options = {})
79
- modifier = [:up, :left].include?(direction) ? -1 : 1
55
+ x, y, x2, y2 = screen.swipe_coordinates(direction)
56
+ @simulator.fire_event(Swipe.new(x, y, x2, y2, 0.015, options))
57
+ sleep(1)
58
+ refresh
59
+ end
60
+
61
+ def drag(source_x, source_y, dest_x, dest_y, options = {})
62
+ @simulator.fire_event(Drag.new(source_x, source_y, dest_x, dest_y, 0.15, options))
63
+ sleep(1)
64
+ refresh
65
+ end
66
+
67
+ def drag_with_source(source, destination)
68
+ sources = source.split(',').collect {|val| val.strip.to_i}
69
+ destinations = destination.split(',').collect {|val| val.strip.to_i}
70
+ drag(sources[0], sources[1], destinations[0], destinations[1])
71
+ end
72
+
73
+ def drag_slider_to(label, direction, distance)
74
+ element = screen.first_slider_element(label)
75
+ x, y = screen.find_slider_button(element)
80
76
 
81
- # Just swipe from the middle of an iPhone-dimensioned screen for now
82
- x = 320 / 2
83
- y = 480 / 2
84
- x2 = x
85
- y2 = y
77
+ dest_x, dest_y = x, y
78
+ modifier = direction_modifier(direction)
86
79
 
87
80
  if [:up, :down].include?(direction)
88
- y2 = y + (y * modifier)
81
+ dest_y += modifier * distance
89
82
  else
90
- x2 = x + (x * modifier)
83
+ dest_x += modifier * distance
91
84
  end
92
85
 
93
- @simulator.fire_event(Swipe.new(x, y, x2, y2, options))
94
-
95
- sleep(1)
96
-
97
- refresh
86
+ drag(x, y, dest_x, dest_y)
98
87
  end
99
-
88
+
89
+ def drag_slider_to_percentage(label, percentage)
90
+ element = screen.first_slider_element(label)
91
+ x, y = screen.find_slider_button(element)
92
+ dest_x, dest_y = screen.find_slider_percentage_location(element, percentage)
93
+ drag(x, y, dest_x, dest_y)
94
+ end
95
+
100
96
  def type(textfield, text, options = {})
101
97
  tap(textfield, :hold_for => 0.75) do |field|
102
98
  if field['value']
@@ -146,16 +142,17 @@ class ICukeWorld
146
142
  end
147
143
 
148
144
  def scroll_to(text, options = {})
145
+ x, y, x2, y2 = screen.swipe_coordinates(swipe_direction(options[:direction]))
149
146
  previous_response = response.dup
150
- while page.xpath(%Q{//*[contains(., "#{text}") or contains(@label, "#{text}") or contains(@value, "#{text}")]}).empty? do
151
- scroll(options[:direction])
152
- raise %Q{Content "#{text}" not found in: #{page}} if response == previous_response
147
+ until screen.visible?(text) do
148
+ @simulator.fire_event(Swipe.new(x, y, x2, y2, 0.15, options))
149
+ refresh
150
+ raise %Q{Content "#{text}" not found in: #{screen}} if response == previous_response
153
151
  end
154
152
  end
155
153
 
156
154
  def scroll(direction)
157
- swipe_directions = { :up => :down, :down => :up, :left => :right, :right => :left }
158
- swipe(swipe_directions[direction])
155
+ swipe(swipe_direction(direction))
159
156
  end
160
157
 
161
158
  def set_application_defaults(defaults)
@@ -164,38 +161,58 @@ class ICukeWorld
164
161
 
165
162
  private
166
163
 
167
- def trait(*traits)
168
- "(#{traits.map { |t| %Q{contains(@traits, "#{t}")} }.join(' or ')})"
169
- end
170
-
171
164
  def refresh
172
165
  @response = nil
173
- @xml = nil
166
+ @screen = nil
174
167
  end
168
+
169
+ def swipe_direction(direction)
170
+ swipe_directions = { :up => :down, :down => :up, :left => :right, :right => :left }
171
+ swipe_directions[direction]
172
+ end
173
+
174
+ def direction_modifier(direction)
175
+ [:up, :left].include?(direction) ? -1 : 1
176
+ end
177
+
175
178
  end
176
179
 
177
180
  World do
178
181
  ICukeWorld.new
179
182
  end
180
183
 
181
- After do
182
- quit
184
+ After do |scenario|
185
+ quit unless scenario.failed?
183
186
  end
184
187
 
185
- LIBICUKE = File.expand_path(File.dirname(__FILE__) + '/../../ext/iCuke/libicuke.dylib')
186
-
187
- Given /^(?:"([^\"]*)" from )?"([^\"]*)" is loaded in the simulator(?: using sdk (.*))?$/ do |target, project, sdk|
188
+ Given /^(?:"([^\"]*)" from )?"([^\"]*)" is loaded in the (?:(iphone|ipad) )?simulator(?: with SDK ([0-9.]+))?$/ do |target, project, platform, sdk_version|
189
+ if sdk_version
190
+ ICuke::SDK.use(sdk_version)
191
+ elsif platform
192
+ ICuke::SDK.use_latest(platform.downcase.to_sym)
193
+ else
194
+ ICuke::SDK.use_latest
195
+ end
196
+
188
197
  launch File.expand_path(project),
189
198
  :target => target,
190
- :env => { 'DYLD_INSERT_LIBRARIES' => LIBICUKE }
199
+ :platform => platform,
200
+ :env => {
201
+ 'DYLD_INSERT_LIBRARIES' => ICuke::SDK.dylib_fullpath
202
+ }
203
+ end
204
+
205
+ Given /^the module "([^\"]*)" is loaded in the simulator$/ do |path|
206
+ path.sub!(/#{File.basename(path)}$/, ICuke::SDK.dylib(File.basename(path)))
207
+ simulator.load_module(File.expand_path(path))
191
208
  end
192
209
 
193
210
  Then /^I should see "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, scope|
194
- raise %Q{Content "#{text}" not found in: #{page}} unless can_see?(text, scope)
211
+ raise %Q{Content "#{text}" not found in: #{screen.xml}} unless screen.visible?(text, scope)
195
212
  end
196
213
 
197
214
  Then /^I should not see "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, scope|
198
- raise %Q{Content "#{text}" was found but was not expected in: #{page}} if can_see?(text, scope)
215
+ raise %Q{Content "#{text}" was found but was not expected in: #{screen.xml}} if screen.visible?(text, scope)
199
216
  end
200
217
 
201
218
  When /^I tap "([^\"]*)"$/ do |label|
@@ -206,6 +223,18 @@ When /^I type "([^\"]*)" in "([^\"]*)"$/ do |text, textfield|
206
223
  type(textfield, text)
207
224
  end
208
225
 
226
+ When /^I drag from (.*) to (.*)$/ do |source, destination|
227
+ drag_with_source(source, destination)
228
+ end
229
+
230
+ When /^I select the "(.*)" slider and drag (.*) pixels (down|up|left|right)$/ do |label, distance, direction|
231
+ drag_slider_to(label, direction.to_sym, distance.to_i)
232
+ end
233
+
234
+ When /^I move the "([^\"]*)" slider to (.*) percent$/ do |label, percent|
235
+ drag_slider_to_percentage(label, percent.to_i)
236
+ end
237
+
209
238
  When /^I scroll (down|up|left|right)(?: to "([^\"]*)")?$/ do |direction, text|
210
239
  if text
211
240
  scroll_to(text, :direction => direction.to_sym)
@@ -219,5 +248,5 @@ Then /^I put the phone into recording mode$/ do
219
248
  end
220
249
 
221
250
  Then /^show me the screen$/ do
222
- puts page.to_s
251
+ puts screen.xml.to_s
223
252
  end
@@ -0,0 +1,110 @@
1
+ require 'nokogiri'
2
+
3
+ class Screen
4
+ attr_reader :xml, :x, :y, :width, :height
5
+
6
+ def initialize(xml)
7
+ @xml = Nokogiri::XML::Document.parse(xml).root
8
+ frame = @xml.at_xpath('/screen/frame')
9
+ @x, @y = frame['x'].to_f, frame['y'].to_f
10
+ @width, @height = frame['width'].to_f, frame['height'].to_f
11
+ end
12
+
13
+ def exists?(text, scope = '')
14
+ find_element(text, scope).any?
15
+ end
16
+
17
+ def visible?(text, scope='')
18
+ find_element(text, scope).each do |element|
19
+ _x, _y = element_center(element)
20
+ return true if _x >= self.x && _y >= self.y && _x < self.width && _y < self.height
21
+ end
22
+ return false
23
+ end
24
+
25
+ def element_center(element)
26
+ frame = element.at_xpath('./frame')
27
+
28
+ x = frame['x'].to_f
29
+ y = frame['y'].to_f
30
+
31
+ x += (frame['width'].to_f / 2)
32
+ y += (frame['height'].to_f / 2)
33
+
34
+ return x, y
35
+ end
36
+
37
+ def first_tappable_element(label)
38
+ element =
39
+ @xml.xpath(
40
+ %Q{//*[#{trait(:button, :updates_frequently, :keyboard_key)} and @label="#{label}" and frame]},
41
+ %Q{//*[#{trait(:link)} and @value="#{label}" and frame]},
42
+ %Q{//*[@label="#{label}" and frame]}
43
+ ).first
44
+ raise %Q{No element labelled "#{label}" found in: #{@xml}} unless element
45
+ element
46
+ end
47
+
48
+ def first_slider_element(label)
49
+ element =
50
+ @xml.xpath(
51
+ %Q{//UISlider[@label="#{label}" and frame]},
52
+ %Q{//*[@label="#{label}"]/../UISlider}
53
+ ).first
54
+ raise %Q{No element labelled "#{label}" found in: #{@xml}} unless element
55
+ element
56
+ end
57
+
58
+ def find_slider_button(element)
59
+ percentage = 0.01 * element['value'].to_f
60
+ calculate_percentage_with_adjustment(element.child, percentage)
61
+ end
62
+
63
+ def find_slider_percentage_location(element, percentage)
64
+ percentage = 0.01 * percentage
65
+ calculate_percentage_with_adjustment(element.child, percentage)
66
+ end
67
+
68
+ def center_coordinates
69
+ @center ||= element_center(@xml.at_xpath('/screen'))
70
+ end
71
+
72
+ def swipe_coordinates(direction)
73
+ modifier = [:up, :left].include?(direction) ? -1 : 1
74
+ x, y = center_coordinates
75
+ x2, y2 = x, y
76
+ if [:up, :down].include?(direction)
77
+ y2 = y + (y * modifier)
78
+ else
79
+ x2 = x + (x * modifier)
80
+ end
81
+ return x, y, x2, y2
82
+ end
83
+
84
+ private
85
+
86
+ def calculate_percentage_with_adjustment(frame, percentage, adjustment=10)
87
+ # need to adjust for padding around control - using 10 pixels default
88
+ x, y = frame['x'].to_f, frame['y'].to_f
89
+ width, height = frame['width'].to_f, frame['height'].to_f
90
+ adjustment = (percentage - 0.5) * (2*adjustment)
91
+ if width < height
92
+ x += width / 2
93
+ y += height * percentage - adjustment
94
+ else
95
+ x += width * percentage - adjustment
96
+ y += height / 2
97
+ end
98
+ return x, y
99
+ end
100
+
101
+ def trait(*traits)
102
+ "(#{traits.map { |t| %Q{contains(@traits, "#{t}")} }.join(' or ')})"
103
+ end
104
+
105
+ def find_element(text, scope='')
106
+ @xml.xpath(
107
+ %Q{#{scope}//*[contains(., "#{text}") or contains(@label, "#{text}") or contains(@value, "#{text}") and frame]}
108
+ )
109
+ end
110
+ end
data/lib/icuke/sdk.rb ADDED
@@ -0,0 +1,134 @@
1
+ module ICuke
2
+ module SDK
3
+ ICUKE_EXT_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'ext', 'iCuke'))
4
+ ICUKE_BIN_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'ext', 'bin'))
5
+ CFLAGS = '-arch i386 -pipe -ggdb -std=c99 -DTARGET_OS_IPHONE'
6
+
7
+ def self.all
8
+ @all ||= begin
9
+ `xcodebuild -showsdks`.grep(/iphonesimulator/).map do |s|
10
+ s.sub(/.* iphonesimulator([0-9.]+).*/, '\1').chomp
11
+ end.sort
12
+ end
13
+ end
14
+
15
+ def self.installed?(sdk)
16
+ all.include?(sdk)
17
+ end
18
+
19
+ def self.major_versions
20
+ all.map { |s| s.split('.').first }.uniq
21
+ end
22
+
23
+ def self.minor_versions
24
+ all.map { |s| s.split('.')[0 .. 1].join('.') }.uniq
25
+ end
26
+
27
+ def self.latest(version = nil)
28
+ @latest ||= version ? all.grep(/^#{version}(?:\.|$)/).last : all.last
29
+ end
30
+
31
+ def self.use(version)
32
+ unless installed?(version)
33
+ raise "The requested SDK version #{version} doesn't appear to be installed"
34
+ end
35
+
36
+ @sdk = version
37
+ end
38
+
39
+ def self.use_latest(major_version = nil)
40
+ case major_version
41
+ when :iphone
42
+ major_version = installed?('4.0') ? '4.0' : '3.1'
43
+ when :ipad
44
+ major_version = installed?('4.0') ? '4.0' : '3.2'
45
+ end
46
+ use latest(major_version)
47
+ end
48
+
49
+ def self.version
50
+ require_sdk
51
+
52
+ @sdk
53
+ end
54
+
55
+ def self.major_version
56
+ require_sdk
57
+
58
+ version.split('.')[0]
59
+ end
60
+
61
+ def self.minor_version
62
+ require_sdk
63
+
64
+ version.split('.')[0 .. 1].join('.')
65
+ end
66
+
67
+ def self.fullname
68
+ require_sdk
69
+
70
+ "iphonesimulator#{version}"
71
+ end
72
+
73
+ def self.root
74
+ require_sdk
75
+
76
+ "#{`xcode-select -print-path`.chomp}/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator#{version}.sdk"
77
+ end
78
+
79
+ def self.home
80
+ require_sdk
81
+
82
+ "#{ENV['HOME']}/Library/Application Support/iPhone Simulator/#{version}"
83
+ end
84
+
85
+ def self.dylib(name = 'libicuke')
86
+ require_sdk
87
+
88
+ "#{name}-sdk#{minor_version}.dylib"
89
+ end
90
+
91
+ def self.dylib_fullpath(name = 'libicuke')
92
+ require_sdk
93
+
94
+ File.join(ICUKE_EXT_DIR, dylib(name))
95
+ end
96
+
97
+ def self.ext_dir
98
+ ICUKE_EXT_DIR
99
+ end
100
+
101
+ def self.sdk_ext_dir
102
+ File.join(ext_dir, "sdk#{minor_version}")
103
+ end
104
+
105
+ def self.cflags
106
+ "#{CFLAGS} -isysroot #{root} -F/System/Library/PrivateFrameworks -D__IPHONE_OS_VERSION_MIN_REQUIRED=#{major_version == '3.1' ? '30000' : '40000'}"
107
+ end
108
+
109
+ def self.gcc
110
+ if major_version == '4'
111
+ abi_flags = "-fobjc-abi-version=2 -fobjc-legacy-dispatch"
112
+ end
113
+ "xcrun -sdk #{fullname} gcc -I. -I#{sdk_ext_dir} -I#{sdk_ext_dir}/json #{cflags} -x objective-c #{abi_flags}"
114
+ end
115
+
116
+ def self.ld
117
+ if major_version == '4'
118
+ abi_flags = "-Xlinker -objc_abi_version -Xlinker 2"
119
+ end
120
+ "xcrun -sdk #{fullname} gcc -I. -I#{sdk_ext_dir} -I#{sdk_ext_dir}/json #{cflags} #{abi_flags}"
121
+ end
122
+
123
+ def self.launch(application, family = :iphone, environment = {})
124
+ environment_args = environment.map { |k, v| %Q{-e "#{k}=#{v}"} }.join(' ')
125
+ %Q{#{ICUKE_BIN_DIR}/waxsim -s #{version} -f #{family} #{environment_args} "#{application}"}
126
+ end
127
+
128
+ private
129
+
130
+ def self.require_sdk
131
+ raise "No SDK has been selected" unless @sdk
132
+ end
133
+ end
134
+ end
@@ -93,23 +93,24 @@ module ICuke
93
93
  end
94
94
 
95
95
  class Swipe
96
- attr_accessor :x, :y, :x2, :y2, :options
96
+ attr_accessor :x, :y, :x2, :y2, :options, :hold_for
97
97
 
98
- def initialize(x, y, x2, y2, options)
98
+ def initialize(x, y, x2, y2, hold_for, options)
99
99
  @options = options
100
100
 
101
101
  @x = x
102
102
  @y = y
103
103
  @x2 = x2
104
104
  @y2 = y2
105
+ @hold_for = hold_for
105
106
  end
106
107
 
107
108
  def to_json(*a)
108
- events = [ICuke::Simulate::Events::Touch.new(:down, [[x, y]], options.merge(:hold_for => 0.015))]
109
+ events = [ICuke::Simulate::Events::Touch.new(:down, [[x, y]], options.merge(:hold_for => hold_for))]
109
110
  each_point([x, y], [x2, y2], 25) do |ix, iy|
110
- events << ICuke::Simulate::Events::Touch.new(:moved, [[ix, iy]], :hold_for => 0.015)
111
+ events << ICuke::Simulate::Events::Touch.new(:moved, [[ix, iy]], :hold_for => hold_for)
111
112
  end
112
- events << ICuke::Simulate::Events::Touch.new(:up, [[x2, y2]], :hold_for => 0.015)
113
+ events << ICuke::Simulate::Events::Touch.new(:up, [[x2, y2]], :hold_for => hold_for)
113
114
  events.to_json(*a)
114
115
  end
115
116
 
@@ -127,6 +128,9 @@ module ICuke
127
128
  end
128
129
  end
129
130
  end
131
+
132
+ class Drag < Swipe
133
+ end
130
134
  end
131
135
  end
132
136
  end
@@ -1,9 +1,5 @@
1
1
  require 'icuke/core_ext'
2
- if ENV['ICUKE_HEADLESS']
3
- require 'icuke/headless'
4
- else
5
- require 'icuke/xcode'
6
- end
2
+ require 'icuke/waxsim'
7
3
 
8
4
  require 'httparty'
9
5
 
@@ -28,28 +24,31 @@ module ICuke
28
24
  end
29
25
 
30
26
  def save(path)
31
- get '/save', :query => URI.escape(path)
27
+ get '/save', :query => path
32
28
  end
33
29
 
34
30
  def load(path)
35
- get '/load', :query => URI.escape(path)
31
+ get '/load', :query => path
36
32
  end
37
33
 
38
34
  def play
39
35
  get '/play'
40
36
  end
41
37
 
38
+ def load_module(path)
39
+ get '/module', :query => path
40
+ end
41
+
42
42
  def fire_event(event)
43
- get '/event', :query => URI.escape(event.to_json)
43
+ get '/event', :query => event.to_json
44
44
  end
45
45
 
46
46
  def set_defaults(defaults)
47
- get '/defaults', :query => URI.escape(defaults.to_json)
47
+ get '/defaults', :query => defaults.to_json
48
48
  end
49
49
 
50
- private
51
-
52
50
  def get(path, options = {})
51
+ options[:query] = URI.escape(options[:query]) if options.has_key?(:query)
53
52
  response = self.class.get(path, options)
54
53
  if response.code != 200
55
54
  raise Simulator::Error, response.body
@@ -0,0 +1,36 @@
1
+ require 'background_process'
2
+ require 'tmpdir'
3
+
4
+ module ICuke
5
+ class Simulator
6
+ include Timeout
7
+
8
+ def launch(project_file, options = {})
9
+ options = {
10
+ :configuration => 'Debug',
11
+ :env => {}
12
+ }.merge(options)
13
+
14
+ app_name = File.basename(project_file, '.xcodeproj')
15
+ directory = "#{File.dirname(project_file)}/build/#{options[:configuration]}-iphonesimulator"
16
+
17
+ options[:env]['CFFIXED_USER_HOME'] = Dir.mktmpdir
18
+
19
+ command = ICuke::SDK.launch("#{directory}/#{app_name}.app", options[:platform], options[:env])
20
+ @simulator = BackgroundProcess.run(command)
21
+
22
+ timeout(30) do
23
+ begin
24
+ view
25
+ rescue Errno::ECONNREFUSED
26
+ sleep(0.5)
27
+ retry
28
+ end
29
+ end
30
+ end
31
+
32
+ def quit
33
+ @simulator.kill('QUIT')
34
+ end
35
+ end
36
+ end
data/lib/icuke.rb CHANGED
@@ -1 +1,2 @@
1
- require 'icuke/simulator'
1
+ require 'icuke/sdk'
2
+ require 'icuke/simulator'