frank-pivotal 1.2.3.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/Gemfile +4 -0
  4. data/Rakefile +38 -0
  5. data/bin/frank +6 -0
  6. data/bin/frank-skeleton +33 -0
  7. data/frank-pivotal.gemspec +37 -0
  8. data/frank-skeleton/features/my_first.feature +12 -0
  9. data/frank-skeleton/features/step_definitions/launch_steps.rb +20 -0
  10. data/frank-skeleton/features/support/env.rb +8 -0
  11. data/frank-skeleton/frank_static_resources.bundle/ViewAttributeMapping.plist +63 -0
  12. data/frank-skeleton/frank_static_resources.bundle/ViewAttributeMappingMac.plist +99 -0
  13. data/frank-skeleton/frank_static_resources.bundle/images/ajax-loader.gif +0 -0
  14. data/frank-skeleton/frank_static_resources.bundle/images/file.gif +0 -0
  15. data/frank-skeleton/frank_static_resources.bundle/images/folder-closed.gif +0 -0
  16. data/frank-skeleton/frank_static_resources.bundle/images/folder.gif +0 -0
  17. data/frank-skeleton/frank_static_resources.bundle/images/loader.gif +0 -0
  18. data/frank-skeleton/frank_static_resources.bundle/images/loader.png +0 -0
  19. data/frank-skeleton/frank_static_resources.bundle/images/minus.gif +0 -0
  20. data/frank-skeleton/frank_static_resources.bundle/images/plus.gif +0 -0
  21. data/frank-skeleton/frank_static_resources.bundle/images/treeview-black-line.gif +0 -0
  22. data/frank-skeleton/frank_static_resources.bundle/images/treeview-black.gif +0 -0
  23. data/frank-skeleton/frank_static_resources.bundle/images/treeview-default-line.gif +0 -0
  24. data/frank-skeleton/frank_static_resources.bundle/images/treeview-default.gif +0 -0
  25. data/frank-skeleton/frank_static_resources.bundle/images/treeview-famfamfam-line.gif +0 -0
  26. data/frank-skeleton/frank_static_resources.bundle/images/treeview-famfamfam.gif +0 -0
  27. data/frank-skeleton/frank_static_resources.bundle/images/treeview-gray-line.gif +0 -0
  28. data/frank-skeleton/frank_static_resources.bundle/images/treeview-gray.gif +0 -0
  29. data/frank-skeleton/frank_static_resources.bundle/images/treeview-red-line.gif +0 -0
  30. data/frank-skeleton/frank_static_resources.bundle/images/treeview-red.gif +0 -0
  31. data/frank-skeleton/frank_static_resources.bundle/index.html +86 -0
  32. data/frank-skeleton/frank_static_resources.bundle/index.html.haml +76 -0
  33. data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.coffee +41 -0
  34. data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.js +46 -0
  35. data/frank-skeleton/frank_static_resources.bundle/js/controller.coffee +134 -0
  36. data/frank-skeleton/frank_static_resources.bundle/js/controller.js +139 -0
  37. data/frank-skeleton/frank_static_resources.bundle/js/details_view.coffee +42 -0
  38. data/frank-skeleton/frank_static_resources.bundle/js/details_view.js +51 -0
  39. data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.coffee +64 -0
  40. data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.js +73 -0
  41. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.coffee +46 -0
  42. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.js +60 -0
  43. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.coffee +167 -0
  44. data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.js +205 -0
  45. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.coffee +10 -0
  46. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.js +17 -0
  47. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.coffee +44 -0
  48. data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.js +63 -0
  49. data/frank-skeleton/frank_static_resources.bundle/js/frank.coffee +96 -0
  50. data/frank-skeleton/frank_static_resources.bundle/js/frank.js +146 -0
  51. data/frank-skeleton/frank_static_resources.bundle/js/lib/backbone.js +1431 -0
  52. data/frank-skeleton/frank_static_resources.bundle/js/lib/coffee-script.js +8 -0
  53. data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery-ui.min.js +405 -0
  54. data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery.min.js +4 -0
  55. data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery.treeview.js +251 -0
  56. data/frank-skeleton/frank_static_resources.bundle/js/lib/json2.js +481 -0
  57. data/frank-skeleton/frank_static_resources.bundle/js/lib/raphael.js +5815 -0
  58. data/frank-skeleton/frank_static_resources.bundle/js/lib/require.js +2053 -0
  59. data/frank-skeleton/frank_static_resources.bundle/js/lib/underscore.js +1059 -0
  60. data/frank-skeleton/frank_static_resources.bundle/js/main.coffee +27 -0
  61. data/frank-skeleton/frank_static_resources.bundle/js/main.js +29 -0
  62. data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.coffee +13 -0
  63. data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.js +22 -0
  64. data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.coffee +15 -0
  65. data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.js +28 -0
  66. data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.coffee +59 -0
  67. data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.js +78 -0
  68. data/frank-skeleton/frank_static_resources.bundle/js/tree_view.coffee +53 -0
  69. data/frank-skeleton/frank_static_resources.bundle/js/tree_view.js +64 -0
  70. data/frank-skeleton/frank_static_resources.bundle/js/view_hier_model.coffee +37 -0
  71. data/frank-skeleton/frank_static_resources.bundle/js/view_hier_model.js +48 -0
  72. data/frank-skeleton/frank_static_resources.bundle/js/view_model.coffee +39 -0
  73. data/frank-skeleton/frank_static_resources.bundle/js/view_model.js +62 -0
  74. data/frank-skeleton/frank_static_resources.bundle/pictos/index.html +329 -0
  75. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.eot +0 -0
  76. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.svg +114 -0
  77. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.ttf +0 -0
  78. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.woff +0 -0
  79. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos.css +20 -0
  80. data/frank-skeleton/frank_static_resources.bundle/pictos/pictos_base64.css +18 -0
  81. data/frank-skeleton/frank_static_resources.bundle/stylesheets/css/symbiote.css +1 -0
  82. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_elements.scss +28 -0
  83. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_header.scss +61 -0
  84. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_inspect_tabs_list_tabs.scss +194 -0
  85. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_jquery.treeview.scss +68 -0
  86. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_jqui.scss +2 -0
  87. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_layout.scss +13 -0
  88. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_mixins.sass +137 -0
  89. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_reset.scss +32 -0
  90. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_selector_test_toolbar.scss +81 -0
  91. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_solarized.scss +16 -0
  92. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_typography.scss +11 -0
  93. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_unicode.scss +3 -0
  94. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_z_index.scss +2 -0
  95. data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/symbiote.scss +26 -0
  96. data/frank-skeleton/frankify.xcconfig.tt +6 -0
  97. data/frank-skeleton/libCocoaAsyncSocket.a +0 -0
  98. data/frank-skeleton/libCocoaAsyncSocketMac.a +0 -0
  99. data/frank-skeleton/libCocoaHTTPServer.a +0 -0
  100. data/frank-skeleton/libCocoaHTTPServerMac.a +0 -0
  101. data/frank-skeleton/libCocoaLumberjack.a +0 -0
  102. data/frank-skeleton/libCocoaLumberjackMac.a +0 -0
  103. data/frank-skeleton/libFrank.a +0 -0
  104. data/frank-skeleton/libFrankMac.a +0 -0
  105. data/frank-skeleton/libShelley.a +0 -0
  106. data/frank-skeleton/libShelleyMac.a +0 -0
  107. data/frank-skeleton/plugins/.empty_directory +0 -0
  108. data/lib/frank-pivotal/app_bundle_locator.rb +58 -0
  109. data/lib/frank-pivotal/bonjour.rb +78 -0
  110. data/lib/frank-pivotal/cli.rb +307 -0
  111. data/lib/frank-pivotal/color_helper.rb +13 -0
  112. data/lib/frank-pivotal/console.rb +28 -0
  113. data/lib/frank-pivotal/core_frank_steps.rb +264 -0
  114. data/lib/frank-pivotal/frank.xcconfig.erb +17 -0
  115. data/lib/frank-pivotal/frank_helper.rb +467 -0
  116. data/lib/frank-pivotal/frank_localize.rb +43 -0
  117. data/lib/frank-pivotal/frank_mac_helper.rb +120 -0
  118. data/lib/frank-pivotal/frankifier.rb +153 -0
  119. data/lib/frank-pivotal/gateway.rb +135 -0
  120. data/lib/frank-pivotal/gesture_helper.rb +99 -0
  121. data/lib/frank-pivotal/host_scripting.rb +102 -0
  122. data/lib/frank-pivotal/keyboard_helper.rb +69 -0
  123. data/lib/frank-pivotal/launcher.rb +118 -0
  124. data/lib/frank-pivotal/localize.yml +104 -0
  125. data/lib/frank-pivotal/location_helper.rb +20 -0
  126. data/lib/frank-pivotal/plugins/plugin.rb +57 -0
  127. data/lib/frank-pivotal/rect.rb +26 -0
  128. data/lib/frank-pivotal/scroll_helper.rb +24 -0
  129. data/lib/frank-pivotal/version.rb +5 -0
  130. data/lib/frank-pivotal/wait_helper.rb +57 -0
  131. data/lib/frank-pivotal.rb +14 -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 +367 -0
@@ -0,0 +1,153 @@
1
+ require 'pathname'
2
+ require 'xcodeproj'
3
+
4
+ class Frankifier
5
+ include Thor::Shell
6
+
7
+ def self.frankify! root_dir, options = {}
8
+ me = new(root_dir, options)
9
+ me.frankify!
10
+ me
11
+ end
12
+
13
+ def initialize( root_dir, options = {} )
14
+ @root = Pathname.new( root_dir )
15
+ @target_build_configuration = options[:build_config]
16
+ @target_selection = options[:target]
17
+ load_xcode_proj_option(options[:project])
18
+ end
19
+
20
+ def frankify!
21
+ decide_on_project if @project.nil?
22
+ decide_on_target
23
+ report_project_and_target
24
+
25
+ check_target_build_configuration_is_valid!
26
+
27
+ say ''
28
+ project_changed |= add_linker_flag
29
+
30
+ say ''
31
+ project_changed |= add_library_search_path
32
+
33
+ if project_changed
34
+ save_changes
35
+ end
36
+ end
37
+
38
+ private
39
+ def load_xcode_proj_option(xcodeproj)
40
+ if xcodeproj
41
+ unless File.exists?(xcodeproj)
42
+ raise "Project file '#{xcodeproj}' does not exist. Please specify the relative path."
43
+ end
44
+
45
+ @xcodeproj_path = Pathname.new(xcodeproj)
46
+ @project = Xcodeproj::Project.open(@xcodeproj_path)
47
+ end
48
+ end
49
+
50
+ def decide_on_project
51
+ projects = Pathname.glob( @root+'*.xcodeproj' )
52
+ xcodeproj = case projects.size
53
+ when 0
54
+ raise "There are no .xcodeproj files in this directory. Please move to your root project directory and try again."
55
+ when 1
56
+ projects.first
57
+ else
58
+ choice = ask_menu(
59
+ "I found more than one .xcodeproj. Which is the main app project that you wish to Frankify?",
60
+ projects.map(&:basename)
61
+ )
62
+ projects[choice]
63
+ end
64
+
65
+ @xcodeproj_path = xcodeproj
66
+ @project = Xcodeproj::Project.open(xcodeproj)
67
+ end
68
+
69
+ def decide_on_target
70
+ targets = @project.targets
71
+ @target = targets.find { |item| item.name.eql?(@target_selection) } if @target_selection
72
+ return @target if @target
73
+ @target = case targets.size
74
+ when 0
75
+ raise "Sorry, this project appears to contain no targets. Nothing I can do here."
76
+ when 1
77
+ targets.first
78
+ else
79
+ choice = ask_menu(
80
+ "I found more than one target in this project. Which is the main app target that you wish to Frankify?",
81
+ targets.map(&:name)
82
+ )
83
+ targets.to_a[choice]
84
+ end
85
+ end
86
+
87
+ def report_project_and_target
88
+ puts "Frankifying target [#{@target.name}] in project #{@xcodeproj_path.basename}"
89
+ end
90
+
91
+ def add_linker_flag
92
+ add_frank_entry_to_build_setting( 'OTHER_LDFLAGS', 'FRANK_LDFLAGS' )
93
+ end
94
+
95
+ def add_library_search_path
96
+ add_frank_entry_to_build_setting( 'LIBRARY_SEARCH_PATHS', 'FRANK_LIBRARY_SEARCH_PATHS' )
97
+ end
98
+
99
+ def add_frank_entry_to_build_setting( build_setting, entry_to_add )
100
+ setting_array = Array( build_settings_to_edit[build_setting] )
101
+
102
+ if setting_array.find{ |flag| flag.start_with? "$(FRANK_" }
103
+ say "It appears that your '#{@target_build_configuration}' configuration's #{build_setting} build setting already include some FRANK setup. Namely: #{setting_array.inspect}. I won't change anything here."
104
+ return false
105
+ end
106
+
107
+ say "Adding $(inherited) and $(#{entry_to_add}) to your '#{@target_build_configuration}' configuration's #{build_setting} build setting ..."
108
+ setting_array.unshift "$(inherited)"
109
+ setting_array << "$(#{entry_to_add})"
110
+ setting_array.uniq! # mainly to avoid duplicate $(inherited) entries
111
+ say "... #{build_setting} is now: #{setting_array.inspect}"
112
+
113
+ build_settings_to_edit[build_setting] = setting_array
114
+ true
115
+ end
116
+
117
+ def check_target_build_configuration_is_valid!
118
+ unless @target.build_configuration_list.build_settings(@target_build_configuration)
119
+ say %Q|I'm trying to Frankify the '#{@target_build_configuration}' build configuration, but I don't see it that build configuration in your XCode target. Here's a list of the build configurations I see:|
120
+ @target.build_configuration_list.build_configurations.each do |bc|
121
+ say " '#{bc.name}'"
122
+ end
123
+ say ''
124
+ say %Q|Please specify one of those build configurations using the --build_configuration flag|
125
+ exit
126
+ end
127
+
128
+ end
129
+
130
+ def build_settings_to_edit
131
+ @_build_settings_to_edit ||= @target.build_configuration_list.build_settings(@target_build_configuration)
132
+ end
133
+
134
+ def save_changes
135
+ @project.save
136
+ end
137
+
138
+ # TODO: send this as a pull request to thor
139
+ def ask_menu message, options
140
+ option_lines = options.each_with_index.map{ |o,i| "#{i+1}: #{o}" }
141
+ full_message = ([message,'']+option_lines+[">"]).join("\n")
142
+
143
+ allowed_range = (1..options.length)
144
+ valid_choice = nil
145
+ until valid_choice
146
+ choice = ask(full_message).to_i
147
+ valid_choice = choice if allowed_range.include?(choice)
148
+ say("You must choose an option between #{allowed_range.first} and #{allowed_range.last}") unless valid_choice
149
+ end
150
+ valid_choice-1
151
+ end
152
+
153
+ end
@@ -0,0 +1,135 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module Frank module Cucumber
5
+
6
+ class Gateway
7
+ DEFAULT_BASE_URL = "http://localhost:37265/"
8
+
9
+ def initialize( base_url = nil )
10
+ @base_url = URI.parse( (base_url || DEFAULT_BASE_URL).to_s )
11
+ end
12
+
13
+ def ping
14
+ send_get('')
15
+ return true
16
+ rescue FrankNetworkError
17
+ return false
18
+ end
19
+
20
+ class << self
21
+ def build_operation_map( method_sig, method_args )
22
+ num_args_according_to_method_sig = method_sig.count(':')
23
+
24
+ if num_args_according_to_method_sig != method_args.count
25
+ raise <<-EOS
26
+
27
+ The method you've specified - #{method_sig} - wants #{num_args_according_to_method_sig} arguments, but you've supplied #{method_args.count} arguments.
28
+
29
+ EOS
30
+ end
31
+
32
+ if num_args_according_to_method_sig > 0 && !method_sig.end_with?(':')
33
+ raise <<-EOS
34
+
35
+ The method signature you've specified - #{method_sig} - is invalid. Objective C method signatures which take parameters must end with a :
36
+
37
+ EOS
38
+ end
39
+
40
+ {
41
+ :method_name => method_sig,
42
+ :arguments => method_args,
43
+ }
44
+ end
45
+
46
+ def evaluate_frankly_response( json, req_desc )
47
+ res = JSON.parse( json )
48
+ if res['outcome'] != 'SUCCESS'
49
+ raise "#{req_desc} failed because: #{res['reason']}\n#{res['details']}"
50
+ end
51
+
52
+ res['results']
53
+ end
54
+ end
55
+
56
+ #taken from Ian Dee's Encumber
57
+ def send_post( verb, command )
58
+ command = command.to_json unless command.is_a? String
59
+
60
+ url = frank_url_for( verb )
61
+ req = Net::HTTP::Post.new url.path
62
+ req.body = command
63
+
64
+ make_http_request( url, req )
65
+ end
66
+
67
+ def send_get( verb )
68
+ url = frank_url_for( verb )
69
+ req = Net::HTTP::Get.new url.path
70
+ make_http_request( url, req )
71
+ end
72
+
73
+ private
74
+
75
+ def frank_url_for( verb )
76
+ url = @base_url.clone
77
+ url.path = '/'+verb
78
+ url
79
+ end
80
+
81
+ def make_http_request( url, req )
82
+ http = Net::HTTP.new(url.host, url.port)
83
+
84
+ begin
85
+ res = http.start do |sess|
86
+ sess.request req
87
+ end
88
+
89
+ res.body
90
+ rescue Errno::ECONNREFUSED
91
+ raise FrankNetworkError
92
+ rescue EOFError
93
+ raise FrankNetworkError
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+
100
+ class FrankNetworkError < RuntimeError
101
+ OVERLY_VERBOSE_YET_HELPFUL_ERROR_MESSAGE = <<EOS
102
+
103
+
104
+ *********************************************
105
+ Oh dear. Your app fell over and can't get up.
106
+ *********************************************
107
+
108
+
109
+ We just encountered an error while trying to talk to the Frank server embedded
110
+ within the app under test. This usually means that the app has just crashed,
111
+ either while carrying out the current step or while finishing up the previous
112
+ step.
113
+
114
+ Here are some things you could do next:
115
+
116
+ - Take a look at the app's logs to see why it crashed. You can view the logs
117
+ in Console.app (a search for 'Frankified' will usually find your frankified
118
+ app's output).
119
+
120
+ - Launch your frankified app in the XCode debugger and then run this scenario
121
+ again. You'll get lots of helpful output from XCode. Don't forget to do
122
+ something to prevent cucumber from automatically re-launching your app when
123
+ you run the scenario by hand. If you don't prevent the relaunch then you
124
+ won't still be in the XCode debugger when the crash happens.
125
+
126
+
127
+
128
+ EOS
129
+
130
+ def initialize
131
+ super OVERLY_VERBOSE_YET_HELPFUL_ERROR_MESSAGE
132
+ end
133
+ end
134
+
135
+ end end
@@ -0,0 +1,99 @@
1
+ module Frank
2
+ module Cucumber
3
+ module GestureHelper
4
+
5
+ # Touch and hold the selector for a given duration
6
+ #
7
+ # @param [String] selector a view selector.
8
+ # @param [Number] duration the duration of the touch, the default is 1.
9
+ #
10
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector whether it was touched or not.
11
+ #
12
+ # @raise an expection if no views matched the selector
13
+ # @raise an expection if no views which matched the selector could be touched
14
+ def tap_and_hold( selector, duration = 1 )
15
+ touch_successes = frankly_map( selector, "touchAndHold:", duration )
16
+ raise "Could not find anything matching [#{selector}] to long touch" if touch_successes.empty?
17
+ raise "Some views could not be long touched (probably because they are not within the current viewport)" if touch_successes.include?(false)
18
+ end
19
+
20
+ # Touch and hold the selector at a specific point for a given duration
21
+ #
22
+ # @param [String] selector a view selector.
23
+ # @param [Number] duration the duration of the touch, the default is 1.
24
+ # @param [Number] x the x-coordinate to touch
25
+ # @param [Number] y the y-coordinate to touch
26
+ #
27
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector whether it was touched or not.
28
+ #
29
+ # @raise an expection if no views matched the selector
30
+ # @raise an expection if no views which matched the selector could be touched
31
+ def tap_and_hold_point( selector, x, y, duration = 1 )
32
+ touch_successes = frankly_map( selector, "touchAndHold:x:y:", duration, x, y )
33
+ raise "Could not find anything matching [#{selector}] to long touch" if touch_successes.empty?
34
+ raise "Some views could not be long touched (probably because they are not within the current viewport)" if touch_successes.include?(false)
35
+ end
36
+
37
+ # Double tap the selector
38
+ #
39
+ # @param [String] selector a view selector.
40
+ #
41
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector whether it was touched or not.
42
+ #
43
+ # @raise an expection if no views matched the selector
44
+ # @raise an expection if no views which matched the selector could be touched
45
+ def double_tap( selector )
46
+ touch_successes = frankly_map( selector, "doubleTap" )
47
+ raise "Could not find anything matching [#{selector}] to double tap" if touch_successes.empty?
48
+ raise "Some views could not be double tap (probably because they are not within the current viewport)" if touch_successes.include?(false)
49
+ end
50
+
51
+ # Double tap the selector at a specific point
52
+ #
53
+ # @param [String] selector a view selector.
54
+ # @param [Number] x the x-coordinate to touch
55
+ # @param [Number] y the y-coordinate to touch
56
+ #
57
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector whether it was touched or not.
58
+ #
59
+ # @raise an expection if no views matched the selector
60
+ # @raise an expection if no views which matched the selector could be touched
61
+ def double_tap_point( selector, x, y )
62
+ touch_successes = frankly_map( selector, "doubleTapx:y:", x, y )
63
+ raise "Could not find anything matching [#{selector}] to double tap" if touch_successes.empty?
64
+ raise "Some views could not be double tap (probably because they are not within the current viewport)" if touch_successes.include?(false)
65
+ end
66
+
67
+ # Drag the slider thumb to required value, taking the specified time
68
+ #
69
+ # @param [String] selector A view selector
70
+ # @param [Number] value The value up to which the slider should be dragged
71
+ # @param [Number] duration The time interval that the drag should take
72
+ #
73
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector if the value was acceptable or not
74
+ #
75
+ # @raise an expection if no views matched the selector
76
+ # @raise an expection if no views which matched the selector could have their thumbs dragged
77
+ def drag_thumb_in_slider( selector, value, duration )
78
+ touch_successes = frankly_map( selector, "FEX_dragThumbToValue:withDuration:", value, duration)
79
+ raise "Could not find anything matching [#{selector}] to have its thumb dragged" if touch_successes.empty?
80
+ raise "Some views could not had their thumbs dragged (are they even UISLiders?)" if touch_successes.include?(false)
81
+ end
82
+
83
+ # Drag the slider thumb to required value
84
+ #
85
+ # @param [String] selector A view selector
86
+ # @param [Number] value The value up to which the slider should be dragged
87
+ #
88
+ # @return [Array<Boolean>] an array indicating for each view which matched the selector if the value was acceptable or not
89
+ #
90
+ # @raise an expection if no views matched the selector
91
+ # @raise an expection if no views which matched the selector could have their thumbs dragged
92
+ def drag_thumb_in_slider_with_default_duration( selector, value )
93
+ touch_successes = frankly_map( selector, "FEX_dragThumbToValue:", value )
94
+ raise "Could not find anything matching [#{selector}] to have its thumb dragged" if touch_successes.empty?
95
+ raise "Some views could not had their thumbs dragged (are they even UISLiders?)" if touch_successes.include?(false)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,102 @@
1
+ require 'frank-cucumber/frank_localize'
2
+
3
+ module Frank module Cucumber
4
+
5
+ module HostScripting
6
+
7
+ def start_recording
8
+ %x{osascript<<APPLESCRIPT
9
+ tell application "QuickTime Player"
10
+ set sr to new screen recording
11
+ tell sr to start
12
+ end tell
13
+ APPLESCRIPT}
14
+
15
+ end
16
+
17
+ def stop_recording
18
+ %x{osascript<<APPLESCRIPT
19
+ tell application "QuickTime Player"
20
+ set sr to (document 1)
21
+ tell sr to stop
22
+ end tell
23
+ APPLESCRIPT}
24
+ end
25
+
26
+ def quit_simulator
27
+ %x{osascript<<APPLESCRIPT-
28
+ application id "com.apple.iphonesimulator" quit
29
+ APPLESCRIPT}
30
+ end
31
+
32
+ def quit_double_simulator
33
+ %x{osascript<<APPLESCRIPT
34
+ activate application id "com.apple.iphonesimulator"
35
+ tell application "System Events"
36
+ set process_name_list to name of (application processes where bundle identifier is "com.apple.iphonesimulator")
37
+ set process_name to first item of process_name_list
38
+ tell process process_name
39
+ if (value of static text 1 of window 1) is "#{Localize.t(:only_one_simulator)}" then
40
+ click button 1 of window 1
41
+ end if
42
+ end tell
43
+ end tell
44
+ }
45
+ end
46
+
47
+ def simulator_reset_data
48
+ %x{osascript<<APPLESCRIPT
49
+ activate application id "com.apple.iphonesimulator"
50
+ tell application "System Events"
51
+ set process_name_list to name of (application processes where bundle identifier is "com.apple.iphonesimulator")
52
+ set process_name to first item of process_name_list
53
+ click menu item 5 of menu 1 of menu bar item 2 of menu bar 1 of process process_name
54
+ delay 0.5
55
+ click button "#{Localize.t(:iphone_simulator_reset)}" of window 1 of process process_name
56
+ end tell
57
+ APPLESCRIPT}
58
+ end
59
+
60
+ #Note this needs to have "Enable access for assistive devices"
61
+ #chcked in the Universal Access system preferences
62
+ def simulator_hardware_menu_press( menu_label )
63
+ %x{osascript -s o <<APPLESCRIPT
64
+ activate application id "com.apple.iphonesimulator"
65
+ tell application "System Events"
66
+ set process_name_list to name of (application processes where bundle identifier is "com.apple.iphonesimulator")
67
+ set process_name to first item of process_name_list
68
+ click menu item "#{menu_label}" of menu "#{Localize.t(:hardware)}" of menu bar 1 of process process_name
69
+ end tell
70
+ APPLESCRIPT}
71
+ end
72
+
73
+ def press_home_on_simulator
74
+ simulator_hardware_menu_press Localize.t(:home)
75
+ end
76
+
77
+ def rotate_simulator_left
78
+ simulator_hardware_menu_press Localize.t(:rotate_left)
79
+ end
80
+
81
+ def rotate_simulator_right
82
+ simulator_hardware_menu_press Localize.t(:rotate_right)
83
+ end
84
+
85
+ def shake_simulator
86
+ simulator_hardware_menu_press Localize.t(:shake_gesture)
87
+ end
88
+
89
+ def simulate_memory_warning
90
+ simulator_hardware_menu_press Localize.t(:simulate_memory_warning)
91
+ end
92
+
93
+ def toggle_call_status_bar
94
+ simulator_hardware_menu_press Localize.t(:toggle_call_status_bar)
95
+ end
96
+
97
+ def simulate_hardware_keyboard
98
+ simulator_hardware_menu_press Localize.t(:simulate_hardware_keyboard)
99
+ end
100
+ end
101
+
102
+ end end
@@ -0,0 +1,69 @@
1
+ module Frank
2
+ module Cucumber
3
+
4
+ module KeyboardHelper
5
+ # Ask Frank to press a sequence of keys on the iOS keyboard.
6
+ #
7
+ # @note The keyboard must be fully visible on the device before calling this method.
8
+ #
9
+ # The "/b" control character is interpreted as a request to press the 'Delete' key.
10
+ #
11
+ # An implicit return is appended to the key sequence, unless you explicitly specify otherwise by setting the :append_return option to false.
12
+ #
13
+ # @example
14
+ # # press the X, -, Y, and z keys on the
15
+ # # iOS keyboard, then press return
16
+ # type_into_keyboard("X-Yz")
17
+ #
18
+ # # press the 1, 2, and 3 keys on the
19
+ # # iOS keyboard, but don't press return afterwards
20
+ # type_into_keyboard("123", :append_return => false)
21
+ #
22
+ # # press the 1, 2, and 3 keys on the
23
+ # # iOS keyboard, but don't press return afterwards
24
+ # type_into_keyboard("123", :append_return => false)
25
+ #
26
+ # # press Delete twice, then type "foo"
27
+ # type_into_keyboard("\b\bfoo")
28
+ #
29
+ def type_into_keyboard(text_to_type, options = {})
30
+ options = {
31
+ :append_return => true
32
+ }.merge(options)
33
+
34
+ if( options[:append_return] )
35
+ text_to_type = text_to_type+"\n" unless text_to_type.end_with?("\n")
36
+ end
37
+ res = frank_server.send_post(
38
+ 'type_into_keyboard',
39
+ :text_to_type => text_to_type
40
+ )
41
+ Frank::Cucumber::Gateway.evaluate_frankly_response( res, "typing the following into the keyboard '#{text_to_type}'" )
42
+ end
43
+
44
+ # Type a keyboard shortcut (Mac only)
45
+ #
46
+ # This function takes as arguments either a single string, or an array of strings, with
47
+ # each string representing a different key to press. Modifier keys are represented by
48
+ # the strings "command", "shift", "option", and "control".
49
+ #
50
+ # For example, to sent a keyboard shortcut to quit the app, you would call
51
+ #
52
+ # type shortcut "command", "q"
53
+ def type_shortcut(*args)
54
+ if args[0].kind_of?(Array)
55
+ return type_shortcut(*args)
56
+ else
57
+ key = args.pop
58
+
59
+ res = frank_server.send_post(
60
+ 'type_into_keyboard',
61
+ :text_to_type => key,
62
+ :modifiers => args
63
+ )
64
+
65
+ Frank::Cucumber::Gateway.evaluate_frankly_response(res, "typing the following shortcut into the keyboard '#{key}' with modifiers #{args}")
66
+ end
67
+ end
68
+ end
69
+ end end
@@ -0,0 +1,118 @@
1
+ require 'sim_launcher'
2
+ require 'frank-cucumber/app_bundle_locator'
3
+ require 'frank-cucumber/frank_helper'
4
+
5
+ module Frank module Cucumber
6
+
7
+ module Launcher
8
+ include Frank::Cucumber::FrankHelper
9
+
10
+ attr_accessor :application_path, :sdk, :version
11
+
12
+ def simulator_client
13
+ @simulator_client ||= SimLauncher::Client.new(@application_path, @sdk, @version)
14
+ end
15
+
16
+ def simulator_direct_client
17
+ @simulator_direct_client ||= SimLauncher::DirectClient.new(@application_path, @sdk, @version)
18
+ end
19
+
20
+ def enforce(app_path, locator = Frank::Cucumber::AppBundleLocator.new)
21
+ if app_path.nil?
22
+ message = "APP_BUNDLE_PATH is not set. \n\nPlease set APP_BUNDLE_PATH (either an environment variable, or the ruby constant in support/env.rb) to the path of your Frankified target's iOS app bundle."
23
+ possible_app_bundles = locator.guess_possible_app_bundles_for_dir( Dir.pwd )
24
+ if possible_app_bundles && !possible_app_bundles.empty?
25
+ message << "\n\nBased on your current directory, you probably want to use one of the following paths for your APP_BUNDLE_PATH:\n"
26
+ message << possible_app_bundles.join("\n")
27
+ end
28
+ raise "\n\n"+("="*80)+"\n"+message+"\n"+("="*80)+"\n\n"
29
+ end
30
+
31
+ if app_path_problem = SimLauncher.check_app_path(app_path)
32
+ raise "\n\n"+("="*80)+"\n"+app_path_problem+"\n"+("="*80)+"\n\n"
33
+ end
34
+ end
35
+
36
+ def launch_app(app_path, sdk = nil, version = 'iphone', wait_for_launch = true)
37
+ @application_path = app_path
38
+ @sdk = sdk
39
+ @version = version
40
+
41
+ if path_is_mac_app(@application_path)
42
+ launch_mac_app(wait_for_launch)
43
+ else
44
+ enforce(app_path)
45
+ launch_ios_app(wait_for_launch)
46
+ end
47
+ end
48
+
49
+ def launch_ios_app(wait_for_launch = true)
50
+ # kill the app if it's already running, just in case this helps
51
+ # reduce simulator flakiness when relaunching the app. Use a timeout of 5 seconds to
52
+ # prevent us hanging around for ages waiting for the ping to fail if the app isn't running
53
+ begin
54
+ Timeout::timeout(5) { press_home_on_simulator if frankly_ping }
55
+ rescue Timeout::Error
56
+ end
57
+
58
+ if( ENV['USE_SIM_LAUNCHER_SERVER'] )
59
+ simulator = simulator_client
60
+ else
61
+ simulator = simulator_direct_client
62
+ end
63
+
64
+ num_timeouts = 0
65
+ begin
66
+ simulator.relaunch
67
+
68
+ if wait_for_launch
69
+ wait_for_frank_to_come_up
70
+ end
71
+ rescue Timeout::Error
72
+ num_timeouts += 1
73
+ puts "Encountered #{num_timeouts} timeouts while launching the app."
74
+ if num_timeouts > 3
75
+ raise "Encountered #{num_timeouts} timeouts in a row while trying to launch the app."
76
+ end
77
+ quit_double_simulator
78
+ retry
79
+ end
80
+
81
+ end
82
+
83
+ def path_is_mac_app (app_dir)
84
+ return File.exists? File.join( app_dir, "Contents", "MacOS" )
85
+ end
86
+
87
+
88
+ def launch_mac_app(wait_for_launch = true)
89
+ `open "#{@application_path}"`
90
+
91
+ if wait_for_launch
92
+ wait_for_frank_to_come_up
93
+ end
94
+ end
95
+
96
+ def quit_mac_app_if_running
97
+ pid = `ps -ax | grep "#{@app_path}" | grep -v grep`
98
+
99
+ if pid != ""
100
+ pid = pid.strip.split[0]
101
+ `kill #{pid}`
102
+ end
103
+
104
+ Timeout::timeout(60) {
105
+ while pid != ""
106
+ pid = `ps -ax | grep "#{@app_path}" | grep -v grep`
107
+ end
108
+ }
109
+
110
+ end
111
+
112
+ def relaunch_mac_app
113
+ self.quit_if_running
114
+ self.launch
115
+ end
116
+
117
+ end
118
+ end end