frank-pivotal 1.2.3.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +38 -0
- data/bin/frank +6 -0
- data/bin/frank-skeleton +33 -0
- data/frank-pivotal.gemspec +37 -0
- data/frank-skeleton/features/my_first.feature +12 -0
- data/frank-skeleton/features/step_definitions/launch_steps.rb +20 -0
- data/frank-skeleton/features/support/env.rb +8 -0
- data/frank-skeleton/frank_static_resources.bundle/ViewAttributeMapping.plist +63 -0
- data/frank-skeleton/frank_static_resources.bundle/ViewAttributeMappingMac.plist +99 -0
- data/frank-skeleton/frank_static_resources.bundle/images/ajax-loader.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/file.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/folder-closed.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/folder.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/loader.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/loader.png +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/minus.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/plus.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-black-line.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-black.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-default-line.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-default.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-famfamfam-line.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-famfamfam.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-gray-line.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-gray.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-red-line.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/images/treeview-red.gif +0 -0
- data/frank-skeleton/frank_static_resources.bundle/index.html +86 -0
- data/frank-skeleton/frank_static_resources.bundle/index.html.haml +76 -0
- data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.coffee +41 -0
- data/frank-skeleton/frank_static_resources.bundle/js/accessible_views_view.js +46 -0
- data/frank-skeleton/frank_static_resources.bundle/js/controller.coffee +134 -0
- data/frank-skeleton/frank_static_resources.bundle/js/controller.js +139 -0
- data/frank-skeleton/frank_static_resources.bundle/js/details_view.coffee +42 -0
- data/frank-skeleton/frank_static_resources.bundle/js/details_view.js +51 -0
- data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.coffee +64 -0
- data/frank-skeleton/frank_static_resources.bundle/js/dropdown_control.js +73 -0
- data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.coffee +46 -0
- data/frank-skeleton/frank_static_resources.bundle/js/ersatz_model.js +60 -0
- data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.coffee +167 -0
- data/frank-skeleton/frank_static_resources.bundle/js/ersatz_view.js +205 -0
- data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.coffee +10 -0
- data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_model.js +17 -0
- data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.coffee +44 -0
- data/frank-skeleton/frank_static_resources.bundle/js/experiment_bar_view.js +63 -0
- data/frank-skeleton/frank_static_resources.bundle/js/frank.coffee +96 -0
- data/frank-skeleton/frank_static_resources.bundle/js/frank.js +146 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/backbone.js +1431 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/coffee-script.js +8 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery-ui.min.js +405 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery.min.js +4 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/jquery.treeview.js +251 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/json2.js +481 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/raphael.js +5815 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/require.js +2053 -0
- data/frank-skeleton/frank_static_resources.bundle/js/lib/underscore.js +1059 -0
- data/frank-skeleton/frank_static_resources.bundle/js/main.coffee +27 -0
- data/frank-skeleton/frank_static_resources.bundle/js/main.js +29 -0
- data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.coffee +13 -0
- data/frank-skeleton/frank_static_resources.bundle/js/tabs_controller.js +22 -0
- data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.coffee +15 -0
- data/frank-skeleton/frank_static_resources.bundle/js/toast_controller.js +28 -0
- data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.coffee +59 -0
- data/frank-skeleton/frank_static_resources.bundle/js/transform_stack.js +78 -0
- data/frank-skeleton/frank_static_resources.bundle/js/tree_view.coffee +53 -0
- data/frank-skeleton/frank_static_resources.bundle/js/tree_view.js +64 -0
- data/frank-skeleton/frank_static_resources.bundle/js/view_hier_model.coffee +37 -0
- data/frank-skeleton/frank_static_resources.bundle/js/view_hier_model.js +48 -0
- data/frank-skeleton/frank_static_resources.bundle/js/view_model.coffee +39 -0
- data/frank-skeleton/frank_static_resources.bundle/js/view_model.js +62 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/index.html +329 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.eot +0 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.svg +114 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.ttf +0 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/pictos-web.woff +0 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/pictos.css +20 -0
- data/frank-skeleton/frank_static_resources.bundle/pictos/pictos_base64.css +18 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/css/symbiote.css +1 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_elements.scss +28 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_header.scss +61 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_inspect_tabs_list_tabs.scss +194 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_jquery.treeview.scss +68 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_jqui.scss +2 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_layout.scss +13 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_mixins.sass +137 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_reset.scss +32 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_selector_test_toolbar.scss +81 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_solarized.scss +16 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_typography.scss +11 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_unicode.scss +3 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/_z_index.scss +2 -0
- data/frank-skeleton/frank_static_resources.bundle/stylesheets/sass/symbiote.scss +26 -0
- data/frank-skeleton/frankify.xcconfig.tt +6 -0
- data/frank-skeleton/libCocoaAsyncSocket.a +0 -0
- data/frank-skeleton/libCocoaAsyncSocketMac.a +0 -0
- data/frank-skeleton/libCocoaHTTPServer.a +0 -0
- data/frank-skeleton/libCocoaHTTPServerMac.a +0 -0
- data/frank-skeleton/libCocoaLumberjack.a +0 -0
- data/frank-skeleton/libCocoaLumberjackMac.a +0 -0
- data/frank-skeleton/libFrank.a +0 -0
- data/frank-skeleton/libFrankMac.a +0 -0
- data/frank-skeleton/libShelley.a +0 -0
- data/frank-skeleton/libShelleyMac.a +0 -0
- data/frank-skeleton/plugins/.empty_directory +0 -0
- data/lib/frank-pivotal/app_bundle_locator.rb +58 -0
- data/lib/frank-pivotal/bonjour.rb +78 -0
- data/lib/frank-pivotal/cli.rb +307 -0
- data/lib/frank-pivotal/color_helper.rb +13 -0
- data/lib/frank-pivotal/console.rb +28 -0
- data/lib/frank-pivotal/core_frank_steps.rb +264 -0
- data/lib/frank-pivotal/frank.xcconfig.erb +17 -0
- data/lib/frank-pivotal/frank_helper.rb +467 -0
- data/lib/frank-pivotal/frank_localize.rb +43 -0
- data/lib/frank-pivotal/frank_mac_helper.rb +120 -0
- data/lib/frank-pivotal/frankifier.rb +153 -0
- data/lib/frank-pivotal/gateway.rb +135 -0
- data/lib/frank-pivotal/gesture_helper.rb +99 -0
- data/lib/frank-pivotal/host_scripting.rb +102 -0
- data/lib/frank-pivotal/keyboard_helper.rb +69 -0
- data/lib/frank-pivotal/launcher.rb +118 -0
- data/lib/frank-pivotal/localize.yml +104 -0
- data/lib/frank-pivotal/location_helper.rb +20 -0
- data/lib/frank-pivotal/plugins/plugin.rb +57 -0
- data/lib/frank-pivotal/rect.rb +26 -0
- data/lib/frank-pivotal/scroll_helper.rb +24 -0
- data/lib/frank-pivotal/version.rb +5 -0
- data/lib/frank-pivotal/wait_helper.rb +57 -0
- data/lib/frank-pivotal.rb +14 -0
- data/test/keyboard_helper_test.rb +84 -0
- data/test/launcher_test.rb +57 -0
- data/test/rect_test.rb +25 -0
- data/test/test_helper.rb +16 -0
- 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
|