sc-frank-cucumber 1.2.1.00af28c

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 (136) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/Rakefile +38 -0
  4. data/bin/frank +6 -0
  5. data/bin/frank-skeleton +33 -0
  6. data/frank-skeleton/features/my_first.feature +12 -0
  7. data/frank-skeleton/features/step_definitions/launch_steps.rb +20 -0
  8. data/frank-skeleton/features/support/env.rb +8 -0
  9. data/frank-skeleton/frank_static_resources.bundle/ViewAttributeMapping.plist +63 -0
  10. data/frank-skeleton/frank_static_resources.bundle/ViewAttributeMappingMac.plist +99 -0
  11. data/frank-skeleton/frank_static_resources.bundle/images/ajax-loader.gif +0 -0
  12. data/frank-skeleton/frank_static_resources.bundle/images/file.gif +0 -0
  13. data/frank-skeleton/frank_static_resources.bundle/images/folder-closed.gif +0 -0
  14. data/frank-skeleton/frank_static_resources.bundle/images/folder.gif +0 -0
  15. data/frank-skeleton/frank_static_resources.bundle/images/loader.gif +0 -0
  16. data/frank-skeleton/frank_static_resources.bundle/images/loader.png +0 -0
  17. data/frank-skeleton/frank_static_resources.bundle/images/minus.gif +0 -0
  18. data/frank-skeleton/frank_static_resources.bundle/images/plus.gif +0 -0
  19. data/frank-skeleton/frank_static_resources.bundle/images/treeview-black-line.gif +0 -0
  20. data/frank-skeleton/frank_static_resources.bundle/images/treeview-black.gif +0 -0
  21. data/frank-skeleton/frank_static_resources.bundle/images/treeview-default-line.gif +0 -0
  22. data/frank-skeleton/frank_static_resources.bundle/images/treeview-default.gif +0 -0
  23. data/frank-skeleton/frank_static_resources.bundle/images/treeview-famfamfam-line.gif +0 -0
  24. data/frank-skeleton/frank_static_resources.bundle/images/treeview-famfamfam.gif +0 -0
  25. data/frank-skeleton/frank_static_resources.bundle/images/treeview-gray-line.gif +0 -0
  26. data/frank-skeleton/frank_static_resources.bundle/images/treeview-gray.gif +0 -0
  27. data/frank-skeleton/frank_static_resources.bundle/images/treeview-red-line.gif +0 -0
  28. data/frank-skeleton/frank_static_resources.bundle/images/treeview-red.gif +0 -0
  29. data/frank-skeleton/frank_static_resources.bundle/index.html +86 -0
  30. data/frank-skeleton/frank_static_resources.bundle/index.html.haml +76 -0
  31. data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.coffee +41 -0
  32. data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.js +46 -0
  33. data/frank-skeleton/frank_static_resources.bundle/js/controller.coffee +134 -0
  34. data/frank-skeleton/frank_static_resources.bundle/js/controller.js +139 -0
  35. data/frank-skeleton/frank_static_resources.bundle/js/details_view.coffee +42 -0
  36. data/frank-skeleton/frank_static_resources.bundle/js/details_view.js +51 -0
  37. data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.coffee +64 -0
  38. data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.js +73 -0
  39. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.coffee +46 -0
  40. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.js +60 -0
  41. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.coffee +167 -0
  42. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.js +205 -0
  43. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.coffee +10 -0
  44. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.js +17 -0
  45. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.coffee +44 -0
  46. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.js +63 -0
  47. data/frank-skeleton/frank_static_resources.bundle/js/frank.coffee +96 -0
  48. data/frank-skeleton/frank_static_resources.bundle/js/frank.js +146 -0
  49. data/frank-skeleton/frank_static_resources.bundle/js/lib/backbone.js +1431 -0
  50. data/frank-skeleton/frank_static_resources.bundle/js/lib/coffee-script.js +8 -0
  51. data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery-ui.min.js +405 -0
  52. data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery.min.js +4 -0
  53. data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery.treeview.js +251 -0
  54. data/frank-skeleton/frank_static_resources.bundle/js/lib/json2.js +481 -0
  55. data/frank-skeleton/frank_static_resources.bundle/js/lib/raphael.js +5815 -0
  56. data/frank-skeleton/frank_static_resources.bundle/js/lib/require.js +2053 -0
  57. data/frank-skeleton/frank_static_resources.bundle/js/lib/underscore.js +1059 -0
  58. data/frank-skeleton/frank_static_resources.bundle/js/main.coffee +27 -0
  59. data/frank-skeleton/frank_static_resources.bundle/js/main.js +29 -0
  60. data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.coffee +13 -0
  61. data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.js +22 -0
  62. data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.coffee +15 -0
  63. data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.js +28 -0
  64. data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.coffee +59 -0
  65. data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.js +78 -0
  66. data/frank-skeleton/frank_static_resources.bundle/js/tree_view.coffee +53 -0
  67. data/frank-skeleton/frank_static_resources.bundle/js/tree_view.js +64 -0
  68. data/frank-skeleton/frank_static_resources.bundle/js/view_hier_model.coffee +37 -0
  69. data/frank-skeleton/frank_static_resources.bundle/js/view_hier_model.js +48 -0
  70. data/frank-skeleton/frank_static_resources.bundle/js/view_model.coffee +39 -0
  71. data/frank-skeleton/frank_static_resources.bundle/js/view_model.js +62 -0
  72. data/frank-skeleton/frank_static_resources.bundle/pictos/index.html +329 -0
  73. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.eot +0 -0
  74. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.svg +114 -0
  75. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.ttf +0 -0
  76. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.woff +0 -0
  77. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos.css +20 -0
  78. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos_base64.css +18 -0
  79. data/frank-skeleton/frank_static_resources.bundle/stylesheets/css/symbiote.css +1 -0
  80. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_elements.scss +28 -0
  81. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_header.scss +61 -0
  82. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_inspect_tabs_list_tabs.scss +194 -0
  83. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_jquery.treeview.scss +68 -0
  84. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_jqui.scss +2 -0
  85. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_layout.scss +13 -0
  86. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_mixins.sass +137 -0
  87. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_reset.scss +32 -0
  88. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_selector_test_toolbar.scss +81 -0
  89. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_solarized.scss +16 -0
  90. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_typography.scss +11 -0
  91. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_unicode.scss +3 -0
  92. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_z_index.scss +2 -0
  93. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/symbiote.scss +26 -0
  94. data/frank-skeleton/frankify.xcconfig.tt +6 -0
  95. data/frank-skeleton/libCocoaAsyncSocket.a +0 -0
  96. data/frank-skeleton/libCocoaAsyncSocketMac.a +0 -0
  97. data/frank-skeleton/libCocoaHTTPServer.a +0 -0
  98. data/frank-skeleton/libCocoaHTTPServerMac.a +0 -0
  99. data/frank-skeleton/libCocoaLumberjack.a +0 -0
  100. data/frank-skeleton/libCocoaLumberjackMac.a +0 -0
  101. data/frank-skeleton/libFrank.a +0 -0
  102. data/frank-skeleton/libFrankMac.a +0 -0
  103. data/frank-skeleton/libShelley.a +0 -0
  104. data/frank-skeleton/libShelleyMac.a +0 -0
  105. data/frank-skeleton/plugins/.empty_directory +0 -0
  106. data/lib/frank-cucumber.rb +15 -0
  107. data/lib/frank-cucumber/app_bundle_locator.rb +58 -0
  108. data/lib/frank-cucumber/bonjour.rb +73 -0
  109. data/lib/frank-cucumber/cli.rb +299 -0
  110. data/lib/frank-cucumber/color_helper.rb +13 -0
  111. data/lib/frank-cucumber/console.rb +28 -0
  112. data/lib/frank-cucumber/core_frank_steps.rb +260 -0
  113. data/lib/frank-cucumber/frank.xcconfig.erb +17 -0
  114. data/lib/frank-cucumber/frank_helper.rb +459 -0
  115. data/lib/frank-cucumber/frank_localize.rb +43 -0
  116. data/lib/frank-cucumber/frank_mac_helper.rb +120 -0
  117. data/lib/frank-cucumber/frankifier.rb +150 -0
  118. data/lib/frank-cucumber/gateway.rb +135 -0
  119. data/lib/frank-cucumber/gesture_helper.rb +99 -0
  120. data/lib/frank-cucumber/host_scripting.rb +96 -0
  121. data/lib/frank-cucumber/keyboard_helper.rb +69 -0
  122. data/lib/frank-cucumber/launcher.rb +70 -0
  123. data/lib/frank-cucumber/localize.yml +104 -0
  124. data/lib/frank-cucumber/location_helper.rb +20 -0
  125. data/lib/frank-cucumber/mac_launcher.rb +35 -0
  126. data/lib/frank-cucumber/plugins/plugin.rb +57 -0
  127. data/lib/frank-cucumber/rect.rb +26 -0
  128. data/lib/frank-cucumber/scroll_helper.rb +24 -0
  129. data/lib/frank-cucumber/version.rb +5 -0
  130. data/lib/frank-cucumber/wait_helper.rb +57 -0
  131. data/sc-frank-cucumber.gemspec +37 -0
  132. data/test/keyboard_helper_test.rb +84 -0
  133. data/test/launcher_test.rb +57 -0
  134. data/test/rect_test.rb +25 -0
  135. data/test/test_helper.rb +16 -0
  136. metadata +395 -0
@@ -0,0 +1,13 @@
1
+ module Frank module Cucumber
2
+
3
+ module ColorHelper
4
+ def colorize(text, color_code)
5
+ "\e[#{color_code}m#{text}\e[0m"
6
+ end
7
+
8
+ def red(text); colorize(text, 31); end
9
+ def green(text); colorize(text, 32); end
10
+ def blue(text); colorize(text, 34); end
11
+ end
12
+
13
+ end end
@@ -0,0 +1,28 @@
1
+ require 'frank-cucumber/frank_helper'
2
+ require 'frank-cucumber/frank_mac_helper'
3
+ require 'frank-cucumber/launcher'
4
+
5
+ module Frank
6
+ class Console
7
+ include Frank::Cucumber::FrankHelper
8
+ include Frank::Cucumber::FrankMacHelper
9
+ include Frank::Cucumber::Launcher
10
+
11
+ def check_for_running_app
12
+ print 'connecting to app...'
13
+ begin
14
+ Timeout::timeout(5) do
15
+ until frankly_ping
16
+ print '.'
17
+ sleep 0.2
18
+ end
19
+ end
20
+ rescue Timeout::Error
21
+ puts ' failed to connect.'
22
+ return false
23
+ end
24
+ puts ' connected'
25
+ return true
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,260 @@
1
+ WAIT_TIMEOUT = ENV['WAIT_TIMEOUT'].to_i || 240
2
+
3
+ require 'rspec/expectations'
4
+
5
+ # -- See -- #
6
+ Then /^I wait to see "([^\"]*)"$/ do |expected_mark|
7
+ quote = get_selector_quote(expected_mark)
8
+ wait_until(:message => "waited to see view marked #{quote}#{expected_mark}#{quote}"){
9
+ view_with_mark_exists( expected_mark )
10
+ }
11
+ end
12
+
13
+ Then /^I wait to not see "([^\"]*)"$/ do |expected_mark|
14
+ sleep 3 # ugh, this should be removed but I'm worried it'll break existing tests
15
+ quote = get_selector_quote(expected_mark)
16
+ wait_until(:message => "waited to not see view marked #{quote}#{expected_mark}#{quote}"){
17
+ !view_with_mark_exists( expected_mark )
18
+ }
19
+ end
20
+
21
+ Then /^I should see a navigation bar titled "([^\"]*)"$/ do |expected_mark|
22
+ quote = get_selector_quote(expected_mark)
23
+ check_element_exists( "navigationItemView marked:#{quote}#{expected_mark}#{quote}" )
24
+ end
25
+
26
+ Then /^I wait to see a navigation bar titled "([^\"]*)"$/ do |expected_mark|
27
+ quote = get_selector_quote(expected_mark)
28
+ wait_until( :timeout => 30, :message => "waited to see a navigation bar titled #{quote}#{expected_mark}#{quote}" ) {
29
+ element_exists( "navigationItemView marked:#{quote}#{expected_mark}#{quote}" )
30
+ }
31
+ end
32
+
33
+ Then /^I wait to not see a navigation bar titled "([^\"]*)"$/ do |expected_mark|
34
+ quote = get_selector_quote(expected_mark)
35
+ wait_until( :timeout => 30, :message => "waited to not see a navigation bar titled #{quote}#{expected_mark}#{quote}" ) {
36
+ !element_exists( "navigationItemView marked:#{quote}#{expected_mark}#{quote}" )
37
+ }
38
+ end
39
+
40
+ Then /^I should see a "([^\"]*)" button$/ do |expected_mark|
41
+ quote = get_selector_quote(expected_mark)
42
+ check_element_exists("button marked:#{quote}#{expected_mark}#{quote}")
43
+ end
44
+
45
+ Then /^I should see "([^\"]*)"$/ do |expected_mark|
46
+ check_view_with_mark_exists(expected_mark)
47
+ end
48
+
49
+ Then /^I should not see "([^\"]*)"$/ do |expected_mark|
50
+ quote = get_selector_quote(expected_mark)
51
+ check_element_does_not_exist_or_is_not_visible("view marked:#{quote}#{expected_mark}#{quote}")
52
+ end
53
+
54
+ Then /I should see the following:/ do |table|
55
+ values = frankly_map( 'view', 'FEX_accessibilityLabel' )
56
+ table.raw.each do |expected_mark|
57
+ values.should include( expected_mark.first )
58
+ end
59
+ end
60
+
61
+ Then /I should not see the following:/ do |table|
62
+ values = frankly_map( 'view', 'FEX_accessibilityLabel' )
63
+ table.raw.each do |expected_mark|
64
+ values.should_not include( expected_mark.first )
65
+ end
66
+ end
67
+
68
+ Then /^I should see an alert view titled "([^\"]*)"$/ do |expected_mark|
69
+ values = frankly_map( 'alertView', 'title')
70
+ values.should include(expected_mark)
71
+ end
72
+
73
+ Then /^I should see an alert view with the message "([^\"]*)"$/ do |expected_mark|
74
+ values = frankly_map( 'alertView', 'message')
75
+ values.should include(expected_mark)
76
+ end
77
+
78
+ Then /^I should not see an alert view$/ do
79
+ check_element_does_not_exist( 'alertView' )
80
+ end
81
+
82
+ Then /^I should see an element of class "([^\"]*)" with name "([^\"]*)" with the following labels: "([^\"]*)"$/ do |className, classLabel, listOfLabels|
83
+ arrayOfLabels = listOfLabels.split(',');
84
+ arrayOfLabels.each do |label|
85
+ quote = get_selector_quote(label)
86
+ check_element_exists("view marked:'#{classLabel}' parent view:'#{className}' descendant view marked:#{quote}#{label}#{quote}")
87
+ end
88
+ end
89
+
90
+ Then /^I should see an element of class "([^\"]*)" with name "([^\"]*)" with a "([^\"]*)" button$/ do |className, classLabel, buttonName|
91
+ quote = get_selector_quote(buttonName)
92
+ check_element_exists("view marked:'#{classLabel}' parent view:'#{className}' descendant button marked:#{quote}#{buttonName}#{quote}")
93
+ end
94
+
95
+ Then /^I should not see a hidden button marked "([^\"]*)"$/ do |expected_mark|
96
+ quote = get_selector_quote(expected_mark)
97
+ element_is_not_hidden("button marked:#{quote}#{expected_mark}#{quote}").should be_false
98
+ end
99
+
100
+ Then /^I should see a nonhidden button marked "([^\"]*)"$/ do |expected_mark|
101
+ quote = get_selector_quote(expected_mark)
102
+ element_is_not_hidden("button marked:#{quote}#{expected_mark}#{quote}").should be_true
103
+ end
104
+
105
+ Then /^I should see an element of class "([^\"]*)"$/ do |className|
106
+ element_is_not_hidden("view:'#{className}'").should be_true
107
+ end
108
+
109
+ Then /^I should not see an element of class "([^\"]*)"$/ do |className|
110
+ selector = "view:'#{className}'"
111
+ element_exists_and_is_not_hidden = element_exists( selector ) && element_is_not_hidden(selector)
112
+ element_exists_and_is_not_hidden.should be_false
113
+ end
114
+
115
+
116
+ # -- Rotate -- #
117
+ Given /^the device is in (a )?(landscape|portrait) orientation$/ do |_,orientation|
118
+ frankly_set_orientation orientation
119
+ end
120
+
121
+ When /^I simulate a memory warning$/ do
122
+ simulate_memory_warning
123
+ end
124
+
125
+ Then /^I rotate to the "([^\"]*)"$/ do |direction|
126
+ if direction == "right"
127
+ rotate_simulator_right
128
+ elsif direction == "left"
129
+ rotate_simulator_left
130
+ else
131
+ raise %Q|Rotation direction specified ("#{direction}") is invalid. Please specify right or left.|
132
+ end
133
+ sleep 1
134
+ end
135
+
136
+ # -- touch -- #
137
+ # generic views
138
+ When /^I touch "([^\"]*)"$/ do |mark|
139
+ quote = get_selector_quote(mark)
140
+ selector = "view marked:#{quote}#{mark}#{quote} first"
141
+ if element_exists(selector)
142
+ touch( selector )
143
+ else
144
+ raise "Could not touch [#{mark}], it does not exist."
145
+ end
146
+ sleep 1
147
+ end
148
+
149
+ When /^I touch "([^\"]*)" if exists$/ do |mark|
150
+ sleep 1
151
+ quote = get_selector_quote(mark)
152
+ selector = "view marked:#{quote}#{mark}#{quote} first"
153
+ if element_exists(selector)
154
+ touch(selector)
155
+ end
156
+ end
157
+
158
+ # table cells
159
+ When /^I touch the first table cell$/ do
160
+ touch("tableViewCell first")
161
+ end
162
+
163
+ When /^I touch the table cell marked "([^\"]*)"$/ do |mark|
164
+ quote = get_selector_quote(mark)
165
+ touch("tableViewCell marked:#{quote}#{mark}#{quote}")
166
+ end
167
+
168
+ When /^I touch the (\d*)(?:st|nd|rd|th)? table cell$/ do |ordinal|
169
+ ordinal = ordinal.to_i - 1
170
+ touch("tableViewCell index:#{ordinal}")
171
+ end
172
+
173
+ Then /I touch the following:/ do |table|
174
+ values = frankly_map( 'view', 'FEX_accessibilityLabel' )
175
+ table.raw.each do |expected_mark|
176
+ quote = get_selector_quote(expected_mark)
177
+ touch( "view marked:#{quote}#{expected_mark}#{quote}" )
178
+ sleep 2
179
+ end
180
+ end
181
+
182
+ # buttons
183
+ When /^I touch the button marked "([^\"]*)"$/ do |mark|
184
+ quote = get_selector_quote(mark)
185
+ touch( "button marked:#{quote}#{mark}#{quote}" )
186
+ end
187
+
188
+ # action sheets
189
+ When /^I touch the "([^\"]*)" action sheet button$/ do |mark|
190
+ quote = get_selector_quote(mark)
191
+ touch( "actionSheet threePartButton marked:#{quote}#{mark}#{quote}" )
192
+ end
193
+
194
+ When /^I touch the (\d*)(?:st|nd|rd|th)? action sheet button$/ do |ordinal|
195
+ ordinal = ordinal.to_i
196
+ touch( "actionSheet threePartButton tag:#{ordinal}" )
197
+ end
198
+
199
+ # alert views
200
+ When /^I touch the "([^\"]*)" alert view button$/ do |mark|
201
+ quote = get_selector_quote(mark)
202
+ touch( "alertView threePartButton marked:#{quote}#{mark}#{quote}" )
203
+ end
204
+
205
+ When /^I touch the (\d*)(?:st|nd|rd|th)? alert view button$/ do |ordinal|
206
+ ordinal = ordinal.to_i
207
+ touch( "alertView threePartButton tag:#{ordinal}" )
208
+ end
209
+
210
+ # -- switch -- #
211
+
212
+ When /^I flip switch "([^\"]*)"$/ do |mark|
213
+ quote = get_selector_quote(mark)
214
+ touch("view:'UISwitch' marked:#{quote}#{mark}#{quote}")
215
+ end
216
+
217
+
218
+ Then /^switch "([^\"]*)" should be (on|off)$/ do |mark,expected_state|
219
+ expected_state = expected_state == 'on'
220
+
221
+ quote = get_selector_quote(mark)
222
+ selector = "view:'UISwitch' marked:#{quote}#{mark}#{quote}"
223
+ switch_states = frankly_map( selector, "isOn" )
224
+
225
+ switch_states.each do |switch_state|
226
+ switch_state.should == expected_state
227
+ end
228
+ end
229
+
230
+ # -- misc -- #
231
+
232
+ When /^I wait for ([\d\.]+) second(?:s)?$/ do |num_seconds|
233
+ num_seconds = num_seconds.to_f
234
+ sleep num_seconds
235
+ end
236
+
237
+ Then /^a pop\-over menu is displayed with the following:$/ do |table|
238
+ sleep 1
239
+ table.raw.each do |expected_mark|
240
+ quote = get_selector_quote(expected_mark)
241
+ check_element_exists "actionSheet view marked:#{quote}#{expected_mark}#{quote}"
242
+ end
243
+ end
244
+
245
+ Then /^I navigate back$/ do
246
+ touch( "navigationItemButtonView" )
247
+ wait_for_nothing_to_be_animating
248
+ end
249
+
250
+ When /^I dump the DOM$/ do
251
+ dom = frankly_dump
252
+ end
253
+
254
+ When /^I quit the simulator/ do
255
+ quit_simulator
256
+ end
257
+
258
+ When /^I reset the simulator/ do
259
+ simulator_reset_data
260
+ end
@@ -0,0 +1,17 @@
1
+ #include "frankify.xcconfig"
2
+ <% plugins.each do |plugin| %>
3
+ #include "plugins/<%=plugin.name%>/<%=plugin.xcconfig%>"
4
+ <% end %>
5
+ INSTALL_PATH = /./
6
+
7
+ <% plugin_names = plugins.map {|plugin| plugin.name } %>
8
+
9
+ FRANK_LDFLAGS_iphoneos = $(FRANK_CORE_LDFLAGS) <%= plugin_names.map {|name| "$(#{name.upcase}_LDFLAGS)"}.join(' ') %>
10
+
11
+ FRANK_LDFLAGS_macosx = $(FRANK_CORE_MAC_LDFLAGS) <%= plugin_names.map {|name| "$(#{name.upcase}_MAC_LDFLAGS)"}.join(' ') %>
12
+
13
+ FRANK_LDFLAGS_iphonesimulator = $(FRANK_LDFLAGS_iphoneos)
14
+
15
+ FRANK_LDFLAGS = $(FRANK_LDFLAGS_$(PLATFORM_NAME))
16
+
17
+ FRANK_GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS = $(FRANK_CORE_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS) <%= plugin_names.map {|name| "$(#{name.upcase}_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS)"}.join(' ') %>
@@ -0,0 +1,459 @@
1
+ require 'json'
2
+ require 'frank-cucumber/gateway'
3
+ require 'frank-cucumber/host_scripting'
4
+ require 'frank-cucumber/wait_helper'
5
+ require 'frank-cucumber/keyboard_helper'
6
+ require 'frank-cucumber/scroll_helper'
7
+ require 'frank-cucumber/gesture_helper'
8
+ require 'frank-cucumber/location_helper'
9
+ require 'frank-cucumber/bonjour'
10
+ require 'frank-cucumber/rect.rb'
11
+
12
+ module Frank module Cucumber
13
+
14
+ # FrankHelper provides a core set of helper functions for use when interacting with Frank.
15
+ #
16
+ # == Most helpful methods
17
+ # * {#touch}
18
+ # * {#wait_for_element_to_exist}
19
+ # * {#wait_for_element_to_exist_and_then_touch_it}
20
+ # * {#wait_for_nothing_to_be_animating}
21
+ # * {#app_exec}
22
+ #
23
+ # == Configuring the Frank driver
24
+ # There are some class-level facilities which configure how all Frank interactions work. For example you can specify which selector engine to use
25
+ # with {FrankHelper.selector_engine}. You can specify the base url which the native app's Frank server is listening on with {FrankHelper.server_base_url}.
26
+ #
27
+ # Two common use cases are covered more conveniently with {FrankHelper.use_shelley_from_now_on} and {FrankHelper.test_on_physical_device_via_bonjour}.
28
+ module FrankHelper
29
+ include WaitHelper
30
+ include KeyboardHelper
31
+ include ScrollHelper
32
+ include GestureHelper
33
+ include HostScripting
34
+ include LocationHelper
35
+
36
+ # @!attribute [rw] selector_engine
37
+ class << self
38
+ # @return [String] the selector engine we tell Frank to use when interpreting view selectors.
39
+ attr_accessor :selector_engine
40
+ # @return [String] the base url which the Frank server is running on. All Frank commands will be sent to that server.
41
+ attr_accessor :server_base_url
42
+
43
+ # After calling this method all subsequent commands will ask Frank to use the Shelley selector engine to interpret view selectors.
44
+ def use_shelley_from_now_on
45
+ @selector_engine = 'shelley_compat'
46
+ end
47
+
48
+ # Use Bonjour to search for a running Frank server. The server found will be the recipient for all subsequent Frank commands.
49
+ # @raise a generic exception if no Frank server could be found via Bonjour
50
+ def test_on_physical_device_via_bonjour
51
+ @server_base_url = Bonjour.new.lookup_frank_base_uri
52
+ raise 'could not detect running Frank server' unless @server_base_url
53
+ end
54
+ end
55
+
56
+ # Get the correct quote for the selector
57
+ def get_selector_quote(selector)
58
+ if selector.index("'") == nil
59
+ return "'"
60
+ else
61
+ return '"'
62
+ end
63
+
64
+ # Specify ip address to run on
65
+ def test_on_physical_device_with_ip(ip_address)
66
+ @server_base_url = ip_address
67
+ raise 'IP Address is incorrect' unless @server_base_url.match(%r{\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b})
68
+ puts "Running on Frank server #{@server_base_url}"
69
+ end
70
+ end
71
+
72
+ #@api private
73
+ #@return [:String] convient shorthand for {Frank::Cucumber::FrankHelper.selector_engine}, defaulting to 'uiquery'
74
+ def selector_engine
75
+ Frank::Cucumber::FrankHelper.selector_engine || 'uiquery' # default to UIQuery for backwards compatibility
76
+ end
77
+
78
+ #@api private
79
+ #@return [:String] convient shorthand for {Frank::Cucumber::FrankHelper.server_base_url}
80
+ def base_server_url
81
+ Frank::Cucumber::FrankHelper.server_base_url
82
+ end
83
+
84
+ # Ask Frank to touch all views matching the specified selector. There may be views in the view heirarchy which match the selector but
85
+ # which Frank cannot or will not touch - for example views which are outside the current viewport. You can discover which of the matching
86
+ # views were actually touched by inspecting the Array which is returned.
87
+ #
88
+ # @param [String] selector a view selector.
89
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector whether it was touched or not.
90
+ # @raise an expection if no views matched the selector
91
+ # @raise an expection if no views which matched the selector could be touched
92
+ def touch( selector )
93
+ touch_successes = frankly_map( selector, 'touch' )
94
+ raise "could not find anything matching [#{selector}] to touch" if touch_successes.empty?
95
+ raise "some views could not be touched (probably because they are not within the current viewport)" if touch_successes.include?(false)
96
+ touch_successes
97
+ end
98
+
99
+ # Fill in text in a text field.
100
+ #
101
+ # @param [String] the placeholder text for the desired text field
102
+ # @param [Hash{Symbol => String}] a hash with a :with key and a string of text to fill in
103
+ # @raise an exception if the :with key DSL syntax is missing
104
+ # @raise an exception if a text field with the given placeholder text could not be found
105
+ def fill_in( placeholder_field_name, options={} )
106
+ raise "Must pass a hash containing the key :with" unless (options.is_a?(Hash) && options.has_key?(:with))
107
+ text_to_type = options[:with]
108
+
109
+ quote = get_selector_quote(placeholder_field_name)
110
+ text_fields_modified = frankly_map( "textField placeholder:#{quote}#{placeholder_field_name}#{quote}", "setText:", text_to_type )
111
+ raise "could not find text fields with placeholder #{quote}#{placeholder_field_name}#{quote}" if text_fields_modified.empty?
112
+ #TODO raise warning if text_fields_modified.count > 1
113
+ end
114
+
115
+ # Indicate whether there are any views in the current view heirarchy which match the specified selector.
116
+ # @param [String] selector a view selector.
117
+ # @return [Boolean]
118
+ # @see #check_element_exists
119
+ def element_exists( selector )
120
+ matches = frankly_map( selector, 'FEX_accessibilityLabel' )
121
+ # TODO: raise warning if matches.count > 1
122
+ !matches.empty?
123
+ end
124
+
125
+ # Assert whether there are any views in the current view heirarchy which match the specified selector.
126
+ # @param [String] selector a view selector.
127
+ # @raise an rspec exception if the assertion fails
128
+ # @see #element_exists, #check_element_does_not_exist
129
+ def check_element_exists( selector )
130
+ element_exists( selector ).should be_true
131
+ end
132
+
133
+ def check_element_exists_and_is_visible( selector )
134
+ element_is_not_hidden( selector ).should be_true
135
+ end
136
+
137
+ # Assert whether there are no views in the current view heirarchy which match the specified selector.
138
+ # @param [String] selector a view selector.
139
+ # @raise an rspec exception if the assertion fails
140
+ # @see #element_exists, #check_element_exists
141
+ def check_element_does_not_exist( selector )
142
+ element_exists( selector ).should be_false
143
+ end
144
+
145
+ def check_element_does_not_exist_or_is_not_visible( selector )
146
+ element_is_not_hidden( selector ).should be_false
147
+ end
148
+
149
+ # Indicate whether there are any views in the current view heirarchy which contain the specified accessibility label.
150
+ # @param [String] expected_mark the expected accessibility label
151
+ # @return [Boolean]
152
+ # @see #check_view_with_mark_exists
153
+ def view_with_mark_exists(expected_mark)
154
+ quote = get_selector_quote(expected_mark)
155
+ element_exists( "view marked:#{quote}#{expected_mark}#{quote}" )
156
+ end
157
+
158
+ # Assert whether there are any views in the current view heirarchy which contain the specified accessibility label.
159
+ # @param [String] expected_mark the expected accessibility label
160
+ # @raise an rspec exception if the assertion fails
161
+ # @see #view_with_mark_exists
162
+ def check_view_with_mark_exists(expected_mark)
163
+ quote = get_selector_quote(expected_mark)
164
+ check_element_exists( "view marked:#{quote}#{expected_mark}#{quote}" )
165
+ end
166
+
167
+ # Assert whether there are no views in the current view heirarchy which contain the specified accessibility label.
168
+ # @param [String] expected_mark the expected accessibility label
169
+ # @raise an rspec exception if the assertion fails
170
+ # @see #view_with_mark_exists, #check_view_with_mark_exists
171
+ def check_view_with_mark_does_not_exist(expected_mark)
172
+ quote = get_selector_quote(expected_mark)
173
+ check_element_does_not_exist( "view marked:#{quote}#{expected_mark}#{quote}" )
174
+ end
175
+
176
+
177
+ # Waits for any of the specified selectors to match a view.
178
+ #
179
+ # Checks each selector in turn within a {http://sauceio.com/index.php/2011/04/how-to-lose-races-and-win-at-selenium/ spin assert} loop and yields the first one which is found to exist in the view heirarchy.
180
+ # Raises an exception if no views could be found to match any of the provided selectors within {WaitHelper::TIMEOUT} seconds.
181
+ #
182
+ # @see WaitHelper#wait_until
183
+ def wait_for_element_to_exist(*selectors,&block)
184
+ wait_until(:message => "Waited for element matching any of #{selectors.join(', ')} to exist") do
185
+ at_least_one_exists = false
186
+ selectors.each do |selector|
187
+ if element_exists( selector )
188
+ at_least_one_exists = true
189
+ block.call(selector) if block
190
+ end
191
+ end
192
+ at_least_one_exists
193
+ end
194
+ end
195
+
196
+ # Waits for the specified selector to not match any views.
197
+ #
198
+ # Uses {WaitHelper#wait_until} to check for any matching views within a {http://sauceio.com/index.php/2011/04/how-to-lose-races-and-win-at-selenium/ spin assert} loop.
199
+ # Returns as soon as no views match the specified selector.
200
+ # Raises an exception if there continued to be at least one view which matched the selector by the time {WaitHelper::TIMEOUT} seconds passed.
201
+ #
202
+ # @see check_element_does_not_exist
203
+ # @see wait_for_element_to_not_exist
204
+ def wait_for_element_to_not_exist(selector)
205
+ wait_until(:message => "Waited for element #{selector} to not exist") do
206
+ !element_exists(selector)
207
+ end
208
+ end
209
+
210
+ # Waits for a view to exist and then send a touch command to that view.
211
+ #
212
+ # @param selectors takes one or more selectors to use to search for a view. The first selector which is found to matches a view is the selector
213
+ # which is then used to send a touch command.
214
+ #
215
+ # Raises an exception if no views could be found to match any of the provided selectors within {WaitHelper::TIMEOUT} seconds.
216
+ def wait_for_element_to_exist_and_then_touch_it(*selectors)
217
+ wait_for_element_to_exist(*selectors) do |sel|
218
+ touch(sel)
219
+ end
220
+ end
221
+
222
+ # Waits for there to be no views which report an isAnimated property of true.
223
+ #
224
+ # @param timeout [Number] number of seconds to wait for nothing to be animating before timeout out. Defaults to {WaitHelper::TIMEOUT}
225
+ #
226
+ # Raises an exception if there were still views animating after {timeout} seconds.
227
+ def wait_for_nothing_to_be_animating( timeout = false )
228
+ wait_until :timeout => timeout do
229
+ !element_exists('view isAnimating')
230
+ end
231
+ end
232
+
233
+
234
+ # Checks that the specified selector matches at least one view, and that at least one of the matched
235
+ # views has an isHidden property set to false
236
+ #
237
+ # a better name for this method would be element_exists_and_is_not_hidden
238
+ def element_is_not_hidden(selector)
239
+ matches = frankly_map( selector, 'FEX_isVisible' )
240
+ matches.delete(false)
241
+ !matches.empty?
242
+ end
243
+
244
+ def accessibility_frame(selector)
245
+ frames = frankly_map( selector, 'FEX_accessibilityFrame' )
246
+ raise "the supplied selector [#{selector}] did not match any views" if frames.empty?
247
+ raise "the supplied selector [#{selector}] matched more than one views (#{frames.count} views matched)" if frames.count > 1
248
+ Rect.from_api_repr( frames.first )
249
+ end
250
+
251
+ def drag_with_initial_delay(args)
252
+ from, to = args.values_at(:from,:to)
253
+ raise ArgumentError.new('must specify a :from parameter') if from.nil?
254
+ raise ArgumentError.new('must specify a :to parameter') if to.nil?
255
+
256
+ dest_frame = accessibility_frame(to)
257
+
258
+ if is_mac
259
+ from_frame = accessibility_frame(from)
260
+
261
+ frankly_map( from, 'FEX_mouseDownX:y:', from_frame.center.x, from_frame.center.y )
262
+
263
+ sleep 0.3
264
+
265
+ frankly_map( from, 'FEX_dragToX:y:', dest_frame.center.x, dest_frame.center.y )
266
+
267
+ sleep 0.3
268
+
269
+ frankly_map( from, 'FEX_mouseUpX:y:', dest_frame.center.x, dest_frame.center.y )
270
+
271
+ else
272
+
273
+ frankly_map( from, 'FEX_dragWithInitialDelayToX:y:', dest_frame.center.x, dest_frame.center.y )
274
+
275
+ end
276
+
277
+ end
278
+
279
+
280
+ # Ask Frank to invoke the specified method on the app delegate of the iOS application under automation.
281
+ # @param method_sig [String] the method signature
282
+ # @param method_args the method arguments
283
+ #
284
+ # @example
285
+ # # the same as calling
286
+ # # [[[UIApplication sharedApplication] appDelegate] setServiceBaseUrl:@"http://example.com/my_api" withPort:8080]
287
+ # # from your native app
288
+ # app_exec( "setServiceBaseUrl:withPort:", "http://example.com/my_api", 8080 )
289
+ #
290
+ #
291
+ def app_exec(method_sig, *method_args)
292
+ operation_map = Gateway.build_operation_map(method_sig.to_s, method_args)
293
+
294
+ res = frank_server.send_post(
295
+ 'app_exec',
296
+ :operation => operation_map
297
+ )
298
+
299
+ return Gateway.evaluate_frankly_response( res, "app_exec #{method_sig}" )
300
+ end
301
+
302
+ # Ask Frank to execute an arbitrary Objective-C method on each view which matches the specified selector.
303
+ #
304
+ # @return [Array] an array with an element for each view matched by the selector, each element in the array gives the return value from invoking the specified method on that view.
305
+ def frankly_map( selector, method_name, *method_args )
306
+ operation_map = Gateway.build_operation_map(method_name.to_s, method_args)
307
+ res = frank_server.send_post(
308
+ 'map',
309
+ :query => selector,
310
+ :operation => operation_map,
311
+ :selector_engine => selector_engine
312
+ )
313
+
314
+ return Gateway.evaluate_frankly_response( res, "frankly_map #{selector} #{method_name}" )
315
+ end
316
+
317
+ # print a JSON-formatted dump of the current view heirarchy to stdout
318
+ def frankly_dump
319
+ res = frank_server.send_get( 'dump' )
320
+ puts JSON.pretty_generate(JSON.parse(res)) rescue puts res #dumping a super-deep DOM causes errors
321
+ end
322
+
323
+ # grab a screenshot of the application under automation and save it to the specified file.
324
+ #
325
+ # @param filename [String] where to save the screenshot image file
326
+ # @param subframe describes which section of the screen to grab. If unspecified then the entire screen will be captured. #TODO document what format this parameter takes.
327
+ # @param allwindows [Boolean] If true then all UIWindows in the current UIScreen will be included in the screenshot. If false then only the main window will be captured.
328
+ def frankly_screenshot(filename, subframe=nil, allwindows=true)
329
+ path = 'screenshot'
330
+ path += '/allwindows' if allwindows
331
+ path += "/frame/" + URI.escape(subframe) if (subframe != nil)
332
+
333
+ data = frank_server.send_get( path )
334
+
335
+ open(filename, "wb") do |file|
336
+ file.write(data)
337
+ end
338
+ end
339
+
340
+ # @return [Boolean] true if the device running the application currently in a portrait orientation
341
+ # @note wil return false if the device is in a flat or unknown orientation. Sometimes the iOS simulator will report this state when first launched.
342
+ def frankly_oriented_portrait?
343
+ 'portrait' == frankly_current_orientation
344
+ end
345
+
346
+ # @return [Boolean] true if the device running the application currently in a landscape orientation
347
+ # @note wil return false if the device is in a flat or unknown orientation. Sometimes the iOS simulator will report this state when first launched.
348
+ def frankly_oriented_landscape?
349
+ 'landscape' == frankly_current_orientation
350
+ end
351
+
352
+ # @return [String] the orientation of the device running the application under automation.
353
+ # @note this is a low-level API. In most cases you should use {frankly_oriented_portrait} or {frankly_oriented_landscape} instead.
354
+ def frankly_current_orientation
355
+ res = frank_server.send_get( 'orientation' )
356
+ orientation = JSON.parse( res )['orientation']
357
+ puts "orientation reported as '#{orientation}'" if $DEBUG
358
+ orientation
359
+ end
360
+
361
+
362
+ # set the device orientation
363
+ # @param orientation can be 'landscape','landscape_left','landscape_right','portrait', or 'portrait_upside_down'
364
+ def frankly_set_orientation(orientation)
365
+ orientation = orientation.to_s
366
+ orientation = 'landscape_left' if orientation == 'landscape'
367
+ res = frank_server.send_post( 'orientation', orientation )
368
+ return Gateway.evaluate_frankly_response( res, "set_orientation #{orientation}" )
369
+ end
370
+
371
+ # @return [Boolean] Does the device running the application have accessibility enabled.
372
+ # If accessibility is not enabled then a lot of Frank functionality will not work.
373
+ def frankly_is_accessibility_enabled
374
+ res = frank_server.send_get( 'accessibility_check' )
375
+ JSON.parse( res )['accessibility_enabled'] == 'true'
376
+ end
377
+
378
+ # wait for the application under automation to be ready to receive automation commands.
379
+ #
380
+ # Has some basic heuristics to cope with cases where the Frank server is intermittently available when first launching.
381
+ #
382
+ # @raise [Timeout::TimeoutError] if nothing is ready within 20 seconds
383
+ # @raise generic error if the device hosting the application does not appear to have accessibility enabled.
384
+ def wait_for_frank_to_come_up
385
+ num_consec_successes = 0
386
+ num_consec_failures = 0
387
+ Timeout.timeout(20) do
388
+ while num_consec_successes <= 6
389
+ if frankly_ping
390
+ num_consec_failures = 0
391
+ num_consec_successes += 1
392
+ else
393
+ num_consec_successes = 0
394
+ num_consec_failures += 1
395
+ if num_consec_failures >= 5 # don't show small timing errors
396
+ print (num_consec_failures == 5 ) ? "\n" : "\r"
397
+ print "PING FAILED" + "!"*num_consec_failures
398
+ end
399
+ end
400
+ STDOUT.flush
401
+ sleep 0.2
402
+ end
403
+
404
+ if num_consec_successes < 6
405
+ print (num_consec_successes == 1 ) ? "\n" : "\r"
406
+ print "FRANK!".slice(0,num_consec_successes)
407
+ STDOUT.flush
408
+ puts ''
409
+ end
410
+
411
+ if num_consec_failures >= 5
412
+ puts ''
413
+ end
414
+ end
415
+
416
+ unless frankly_is_accessibility_enabled
417
+ raise "ACCESSIBILITY DOES NOT APPEAR TO BE ENABLED ON YOUR SIMULATOR. Hit the home button, go to settings, select Accessibility, and turn the inspector on."
418
+ end
419
+ end
420
+
421
+ # @return [String] the name of the device currently running the application
422
+ # @note this is a low-level API. In most cases you should use {is_iphone}, {is_ipad} or {is_mac} instead.
423
+ def frankly_device_name
424
+ res = frank_server.send_get( 'device' )
425
+ device = JSON.parse( res )['device']
426
+ puts "device reported as '#{device}'" if $DEBUG
427
+ device
428
+ end
429
+
430
+ # @return [Boolean] is the device running the application an iPhone.
431
+ def is_iphone
432
+ return frankly_device_name == "iphone"
433
+ end
434
+
435
+ # @return [Boolean] is the device running the application an iPhone.
436
+ def is_ipad
437
+ return frankly_device_name == "ipad"
438
+ end
439
+
440
+ # @return [Boolean] is the device running the application a Mac.
441
+ def is_mac
442
+ return frankly_device_name == "mac"
443
+ end
444
+
445
+ # Check whether Frank is able to communicate with the application under automation
446
+ def frankly_ping
447
+ frank_server.ping
448
+ end
449
+
450
+ #@api private
451
+ #@return [Frank::Cucumber::Gateway] a gateway for sending Frank commands to the application under automation
452
+ def frank_server
453
+ @_frank_server ||= Frank::Cucumber::Gateway.new( base_server_url )
454
+ end
455
+
456
+ end
457
+
458
+
459
+ end end