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.
- data/.gitignore +2 -0
- data/.gitmodules +3 -0
- data/README.rdoc +8 -7
- data/Rakefile +10 -30
- data/VERSION +1 -1
- data/app/.gitignore +3 -0
- data/app/LICENSE +20 -0
- data/app/Readme.markdown +3 -0
- data/app/Shared/AppDelegate_Shared.h +17 -0
- data/app/Shared/AppDelegate_Shared.m +25 -0
- data/app/{images/yellowslide.png → Shared/Icon.png} +0 -0
- data/app/Shared/ModalViewController.h +15 -0
- data/app/Shared/ModalViewController.m +96 -0
- data/app/Shared/TestViewController_Shared.h +15 -0
- data/app/Shared/TestViewController_Shared.m +43 -0
- data/app/{Info.plist → Shared/Universal-Info.plist} +17 -4
- data/app/Shared/Universal_Prefix.pch +8 -0
- data/app/Shared/main.m +14 -0
- data/app/Universal.xcodeproj/project.pbxproj +301 -0
- data/app/iPad/AppDelegate_Pad.h +15 -0
- data/app/iPad/AppDelegate_Pad.m +28 -0
- data/app/iPad/Default-Landscape.png +0 -0
- data/app/iPad/Default-Portrait.png +0 -0
- data/app/iPad/MainWindow_Pad.xib +343 -0
- data/app/iPad/TestViewController_Pad.h +15 -0
- data/app/iPad/TestViewController_Pad.m +52 -0
- data/app/iPhone/AppDelegate_Phone.h +15 -0
- data/app/iPhone/AppDelegate_Phone.m +28 -0
- data/app/{images/segment_search.png → iPhone/Default.png} +0 -0
- data/app/iPhone/MainWindow_Phone.xib +363 -0
- data/app/iPhone/TestViewController_Phone.h +15 -0
- data/app/iPhone/TestViewController_Phone.m +36 -0
- data/bin/.gitignore +1 -0
- data/bin/icuke +10 -0
- data/bin/icuke-module +17 -0
- data/ext/.gitignore +1 -0
- data/ext/Rakefile +6 -0
- data/ext/WaxSim/Simulator.h +31 -0
- data/ext/WaxSim/Simulator.m +131 -0
- data/ext/WaxSim/WaxSim.m +110 -0
- data/ext/WaxSim/WaxSim.xcodeproj/project.pbxproj +238 -0
- data/ext/WaxSim/WaxSim_Prefix.pch +7 -0
- data/ext/WaxSim/iPhoneSimulatorRemoteClient/iPhoneSimulatorRemoteClient.h +126 -0
- data/ext/iCuke/Rakefile +14 -17
- data/ext/iCuke/libicuke-sdk3.1.dylib +0 -0
- data/ext/iCuke/libicuke-sdk4.0.dylib +0 -0
- data/ext/iCuke/libicuke.dylib +0 -0
- data/ext/iCuke/{DefaultsResponse.h → sdk3.1/DefaultsResponse.h} +0 -0
- data/ext/iCuke/{DefaultsResponse.m → sdk3.1/DefaultsResponse.m} +0 -0
- data/ext/iCuke/{EventResponse.h → sdk3.1/EventResponse.h} +0 -0
- data/ext/iCuke/{EventResponse.m → sdk3.1/EventResponse.m} +0 -0
- data/ext/iCuke/sdk3.1/ModuleResponse.h +5 -0
- data/ext/iCuke/sdk3.1/ModuleResponse.m +76 -0
- data/ext/iCuke/sdk3.1/Rakefile +20 -0
- data/ext/iCuke/{Recorder.h → sdk3.1/Recorder.h} +0 -0
- data/ext/iCuke/{Recorder.m → sdk3.1/Recorder.m} +0 -0
- data/ext/iCuke/{RecorderResponse.h → sdk3.1/RecorderResponse.h} +0 -0
- data/ext/iCuke/{RecorderResponse.m → sdk3.1/RecorderResponse.m} +0 -0
- data/ext/iCuke/{SynthesizeSingleton.h → sdk3.1/SynthesizeSingleton.h} +0 -0
- data/ext/iCuke/{ViewResponse.h → sdk3.1/ViewResponse.h} +0 -0
- data/ext/iCuke/{ViewResponse.m → sdk3.1/ViewResponse.m} +0 -0
- data/ext/iCuke/{Viewer.h → sdk3.1/Viewer.h} +0 -0
- data/ext/iCuke/sdk3.1/Viewer.m +203 -0
- data/ext/iCuke/{iCukeHTTPResponseHandler.h → sdk3.1/iCukeHTTPResponseHandler.h} +0 -0
- data/ext/iCuke/{iCukeHTTPResponseHandler.m → sdk3.1/iCukeHTTPResponseHandler.m} +0 -0
- data/ext/iCuke/{iCukeHTTPServer.h → sdk3.1/iCukeHTTPServer.h} +0 -0
- data/ext/iCuke/{iCukeHTTPServer.m → sdk3.1/iCukeHTTPServer.m} +0 -0
- data/ext/iCuke/{iCukeServer.h → sdk3.1/iCukeServer.h} +0 -2
- data/ext/iCuke/{iCukeServer.m → sdk3.1/iCukeServer.m} +22 -20
- data/ext/iCuke/{json → sdk3.1/json}/JSON.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/NSObject+SBJSON.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/NSObject+SBJSON.m +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/NSString+SBJSON.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/NSString+SBJSON.m +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJSON.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJSON.m +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJsonBase.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJsonBase.m +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJsonParser.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJsonParser.m +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJsonWriter.h +0 -0
- data/ext/iCuke/{json → sdk3.1/json}/SBJsonWriter.m +0 -0
- data/ext/iCuke/sdk4.0/DefaultsResponse.h +5 -0
- data/ext/iCuke/sdk4.0/DefaultsResponse.m +66 -0
- data/ext/iCuke/sdk4.0/EventResponse.h +5 -0
- data/ext/iCuke/sdk4.0/EventResponse.m +130 -0
- data/ext/iCuke/sdk4.0/ModuleResponse.h +5 -0
- data/ext/iCuke/sdk4.0/ModuleResponse.m +76 -0
- data/ext/iCuke/sdk4.0/Rakefile +20 -0
- data/ext/iCuke/sdk4.0/Recorder.h +17 -0
- data/ext/iCuke/sdk4.0/Recorder.m +90 -0
- data/ext/iCuke/sdk4.0/RecorderResponse.h +6 -0
- data/ext/iCuke/sdk4.0/RecorderResponse.m +68 -0
- data/ext/iCuke/sdk4.0/SynthesizeSingleton.h +68 -0
- data/ext/iCuke/sdk4.0/ViewResponse.h +5 -0
- data/ext/iCuke/sdk4.0/ViewResponse.m +84 -0
- data/ext/iCuke/sdk4.0/Viewer.h +8 -0
- data/ext/iCuke/{Viewer.m → sdk4.0/Viewer.m} +14 -0
- data/ext/iCuke/sdk4.0/iCukeHTTPResponseHandler.h +50 -0
- data/ext/iCuke/sdk4.0/iCukeHTTPResponseHandler.m +381 -0
- data/ext/iCuke/sdk4.0/iCukeHTTPServer.h +53 -0
- data/ext/iCuke/sdk4.0/iCukeHTTPServer.m +365 -0
- data/ext/iCuke/sdk4.0/iCukeServer.h +14 -0
- data/ext/iCuke/sdk4.0/iCukeServer.m +65 -0
- data/ext/iCuke/sdk4.0/json/JSON.h +50 -0
- data/ext/iCuke/sdk4.0/json/NSObject+SBJSON.h +68 -0
- data/ext/iCuke/sdk4.0/json/NSObject+SBJSON.m +53 -0
- data/ext/iCuke/sdk4.0/json/NSString+SBJSON.h +58 -0
- data/ext/iCuke/sdk4.0/json/NSString+SBJSON.m +55 -0
- data/ext/iCuke/sdk4.0/json/SBJSON.h +75 -0
- data/ext/iCuke/sdk4.0/json/SBJSON.m +212 -0
- data/ext/iCuke/sdk4.0/json/SBJsonBase.h +86 -0
- data/ext/iCuke/sdk4.0/json/SBJsonBase.m +78 -0
- data/ext/iCuke/sdk4.0/json/SBJsonParser.h +87 -0
- data/ext/iCuke/sdk4.0/json/SBJsonParser.m +475 -0
- data/ext/iCuke/sdk4.0/json/SBJsonWriter.h +129 -0
- data/ext/iCuke/sdk4.0/json/SBJsonWriter.m +228 -0
- data/features/iphone.feature +15 -0
- data/features/support/env.rb +1 -1
- data/iCuke.gemspec +118 -114
- data/lib/icuke/cucumber.rb +93 -64
- data/lib/icuke/screen.rb +110 -0
- data/lib/icuke/sdk.rb +134 -0
- data/lib/icuke/simulate.rb +9 -5
- data/lib/icuke/simulator.rb +10 -11
- data/lib/icuke/waxsim.rb +36 -0
- data/lib/icuke.rb +2 -1
- metadata +124 -129
- data/app/AlertsViewController.h +0 -59
- data/app/AlertsViewController.m +0 -341
- data/app/AppDelegate.h +0 -60
- data/app/AppDelegate.m +0 -101
- data/app/ButtonsViewController.h +0 -80
- data/app/ButtonsViewController.m +0 -448
- data/app/Constants.h +0 -58
- data/app/ControlsViewController.h +0 -72
- data/app/ControlsViewController.m +0 -379
- data/app/ImagesViewController.h +0 -62
- data/app/ImagesViewController.m +0 -137
- data/app/MainViewController.h +0 -57
- data/app/MainViewController.m +0 -262
- data/app/Picker/CustomPickerDataSource.h +0 -55
- data/app/Picker/CustomPickerDataSource.m +0 -135
- data/app/Picker/CustomView.h +0 -62
- data/app/Picker/CustomView.m +0 -119
- data/app/PickerViewController.h +0 -88
- data/app/PickerViewController.m +0 -402
- data/app/Prefix.pch +0 -5
- data/app/ReadMe.txt +0 -82
- data/app/SearchBarController.h +0 -58
- data/app/SearchBarController.m +0 -105
- data/app/SegmentViewController.h +0 -54
- data/app/SegmentViewController.m +0 -195
- data/app/TextFieldController.h +0 -67
- data/app/TextFieldController.m +0 -342
- data/app/TextViewController.h +0 -57
- data/app/TextViewController.m +0 -164
- data/app/ToolbarViewController.h +0 -74
- data/app/ToolbarViewController.m +0 -303
- data/app/TransitionViewController.h +0 -64
- data/app/TransitionViewController.m +0 -178
- data/app/UICatalog.xcodeproj/project.pbxproj +0 -615
- data/app/WebViewController.h +0 -57
- data/app/WebViewController.m +0 -173
- data/app/en.lproj/AlertsViewController.xib +0 -144
- data/app/en.lproj/ButtonsViewController.xib +0 -147
- data/app/en.lproj/ControlsViewController.xib +0 -147
- data/app/en.lproj/ImagesViewController.xib +0 -264
- data/app/en.lproj/Localizable.strings +0 -41
- data/app/en.lproj/MainWindow.xib +0 -306
- data/app/en.lproj/PickerViewController.xib +0 -415
- data/app/en.lproj/SearchBarController.xib +0 -142
- data/app/en.lproj/SegmentViewController.xib +0 -143
- data/app/en.lproj/TextFieldController.xib +0 -167
- data/app/en.lproj/TextViewController.xib +0 -149
- data/app/en.lproj/ToolbarViewController.xib +0 -491
- data/app/en.lproj/TransitionViewController.xib +0 -255
- data/app/en.lproj/WebViewController.xib +0 -141
- data/app/images/12-6AM.png +0 -0
- data/app/images/12-6PM.png +0 -0
- data/app/images/6-12AM.png +0 -0
- data/app/images/6-12PM.png +0 -0
- data/app/images/Default.png +0 -0
- data/app/images/Icon.png +0 -0
- data/app/images/UIButton_custom.png +0 -0
- data/app/images/blueButton.png +0 -0
- data/app/images/orangeslide.png +0 -0
- data/app/images/scene1.jpg +0 -0
- data/app/images/scene2.jpg +0 -0
- data/app/images/scene3.jpg +0 -0
- data/app/images/scene4.jpg +0 -0
- data/app/images/scene5.jpg +0 -0
- data/app/images/segment_check.png +0 -0
- data/app/images/segment_tools.png +0 -0
- data/app/images/slider_ball.png +0 -0
- data/app/images/whiteButton.png +0 -0
- data/app/main.m +0 -58
- data/features/uicatalog.feature +0 -20
- data/lib/icuke/com.apple.Accessibility.plist +0 -0
- data/lib/icuke/headless.rb +0 -54
- data/lib/icuke/xcode.rb +0 -164
data/lib/icuke/cucumber.rb
CHANGED
|
@@ -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
|
|
24
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
-
|
|
81
|
+
dest_y += modifier * distance
|
|
89
82
|
else
|
|
90
|
-
|
|
83
|
+
dest_x += modifier * distance
|
|
91
84
|
end
|
|
92
85
|
|
|
93
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
:
|
|
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: #{
|
|
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: #{
|
|
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
|
|
251
|
+
puts screen.xml.to_s
|
|
223
252
|
end
|
data/lib/icuke/screen.rb
ADDED
|
@@ -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
|
data/lib/icuke/simulate.rb
CHANGED
|
@@ -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 =>
|
|
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 =>
|
|
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 =>
|
|
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
|
data/lib/icuke/simulator.rb
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
require 'icuke/core_ext'
|
|
2
|
-
|
|
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 =>
|
|
27
|
+
get '/save', :query => path
|
|
32
28
|
end
|
|
33
29
|
|
|
34
30
|
def load(path)
|
|
35
|
-
get '/load', :query =>
|
|
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 =>
|
|
43
|
+
get '/event', :query => event.to_json
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def set_defaults(defaults)
|
|
47
|
-
get '/defaults', :query =>
|
|
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
|
data/lib/icuke/waxsim.rb
ADDED
|
@@ -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/
|
|
1
|
+
require 'icuke/sdk'
|
|
2
|
+
require 'icuke/simulator'
|