cukunity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitmodules +0 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +60 -0
- data/LICENSE.txt +20 -0
- data/README.md +214 -0
- data/Rakefile +51 -0
- data/Unity/Assets/Demo/Art/Buttons/down.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/down.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/left.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/left.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/ok.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/ok.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/options.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/options.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/play.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/play.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/quit.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/quit.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/retry.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/retry.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/right.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/right.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons/up.png +0 -0
- data/Unity/Assets/Demo/Art/Buttons/up.png.meta +28 -0
- data/Unity/Assets/Demo/Art/Buttons.meta +2 -0
- data/Unity/Assets/Demo/Art/clear.png +0 -0
- data/Unity/Assets/Demo/Art/clear.png.meta +28 -0
- data/Unity/Assets/Demo/Art/options.png +0 -0
- data/Unity/Assets/Demo/Art/options.png.meta +28 -0
- data/Unity/Assets/Demo/Art/sokoban.png +0 -0
- data/Unity/Assets/Demo/Art/sokoban.png.meta +28 -0
- data/Unity/Assets/Demo/Art.meta +2 -0
- data/Unity/Assets/Demo/Cubes/CrateCube.mat +0 -0
- data/Unity/Assets/Demo/Cubes/CrateCube.mat.meta +2 -0
- data/Unity/Assets/Demo/Cubes/CrateCube.prefab +0 -0
- data/Unity/Assets/Demo/Cubes/CrateCube.prefab.meta +2 -0
- data/Unity/Assets/Demo/Cubes/FloorCube.mat +0 -0
- data/Unity/Assets/Demo/Cubes/FloorCube.mat.meta +2 -0
- data/Unity/Assets/Demo/Cubes/FloorCube.prefab +0 -0
- data/Unity/Assets/Demo/Cubes/FloorCube.prefab.meta +2 -0
- data/Unity/Assets/Demo/Cubes/GoalCube.mat +0 -0
- data/Unity/Assets/Demo/Cubes/GoalCube.mat.meta +2 -0
- data/Unity/Assets/Demo/Cubes/GoalCube.prefab +0 -0
- data/Unity/Assets/Demo/Cubes/GoalCube.prefab.meta +2 -0
- data/Unity/Assets/Demo/Cubes/PlayerCube.mat +0 -0
- data/Unity/Assets/Demo/Cubes/PlayerCube.mat.meta +2 -0
- data/Unity/Assets/Demo/Cubes/PlayerCube.prefab +0 -0
- data/Unity/Assets/Demo/Cubes/PlayerCube.prefab.meta +2 -0
- data/Unity/Assets/Demo/Cubes/WallCube.mat +0 -0
- data/Unity/Assets/Demo/Cubes/WallCube.mat.meta +2 -0
- data/Unity/Assets/Demo/Cubes/WallCube.prefab +0 -0
- data/Unity/Assets/Demo/Cubes/WallCube.prefab.meta +2 -0
- data/Unity/Assets/Demo/Cubes.meta +2 -0
- data/Unity/Assets/Demo/Resources/MainMenu.prefab +0 -0
- data/Unity/Assets/Demo/Resources/MainMenu.prefab.meta +2 -0
- data/Unity/Assets/Demo/Resources/OptionsMenu.prefab +0 -0
- data/Unity/Assets/Demo/Resources/OptionsMenu.prefab.meta +2 -0
- data/Unity/Assets/Demo/Resources.meta +2 -0
- data/Unity/Assets/Demo/Scripts/CameraController.js +18 -0
- data/Unity/Assets/Demo/Scripts/CameraController.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/EnterPlayerName.js +21 -0
- data/Unity/Assets/Demo/Scripts/EnterPlayerName.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/GameLeftButton.js +18 -0
- data/Unity/Assets/Demo/Scripts/GameLeftButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/GameQuitButton.js +6 -0
- data/Unity/Assets/Demo/Scripts/GameQuitButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/GameRetryButton.js +7 -0
- data/Unity/Assets/Demo/Scripts/GameRetryButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/GameRightButton.js +18 -0
- data/Unity/Assets/Demo/Scripts/GameRightButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/GameUpButton.js +18 -0
- data/Unity/Assets/Demo/Scripts/GameUpButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/OnTouchGUIElementDown.js +34 -0
- data/Unity/Assets/Demo/Scripts/OnTouchGUIElementDown.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/PlayerData.js +3 -0
- data/Unity/Assets/Demo/Scripts/PlayerData.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/SokobanController.js +240 -0
- data/Unity/Assets/Demo/Scripts/SokobanController.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/TakeScreenShot.js +26 -0
- data/Unity/Assets/Demo/Scripts/TakeScreenShot.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/TitleOptionsButton.js +8 -0
- data/Unity/Assets/Demo/Scripts/TitleOptionsButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/TitleOptionsOkButton.js +8 -0
- data/Unity/Assets/Demo/Scripts/TitleOptionsOkButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/TitlePlayButton.js +6 -0
- data/Unity/Assets/Demo/Scripts/TitlePlayButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/TitleQuitButton.js +7 -0
- data/Unity/Assets/Demo/Scripts/TitleQuitButton.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/UpdateMovesLabel.js +26 -0
- data/Unity/Assets/Demo/Scripts/UpdateMovesLabel.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts/UpdatePlayerName.js +14 -0
- data/Unity/Assets/Demo/Scripts/UpdatePlayerName.js.meta +2 -0
- data/Unity/Assets/Demo/Scripts.meta +2 -0
- data/Unity/Assets/Demo/demo_game.unity +0 -0
- data/Unity/Assets/Demo/demo_game.unity.meta +2 -0
- data/Unity/Assets/Demo/demo_title.unity +0 -0
- data/Unity/Assets/Demo/demo_title.unity.meta +2 -0
- data/Unity/Assets/Demo.meta +2 -0
- data/Unity/Assets/Plugins/CukunityInstance.prefab +0 -0
- data/Unity/Assets/Plugins/CukunityInstance.prefab.meta +2 -0
- data/Unity/Assets/Plugins/CukunityServer.cs +871 -0
- data/Unity/Assets/Plugins/CukunityServer.cs.meta +2 -0
- data/Unity/Assets/Plugins/JsonFx.Json.dll +0 -0
- data/Unity/Assets/Plugins/JsonFx.Json.dll.meta +2 -0
- data/Unity/Assets/Plugins/iOS/CukunityServer.mm +242 -0
- data/Unity/Assets/Plugins/iOS/CukunityServer.mm.meta +2 -0
- data/Unity/Assets/Plugins/iOS.meta +2 -0
- data/Unity/Assets/Plugins.meta +2 -0
- data/Unity/Library/AudioManager.asset +0 -0
- data/Unity/Library/BuildSettings.asset +0 -0
- data/Unity/Library/DynamicsManager.asset +0 -0
- data/Unity/Library/EditorBuildSettings.asset +0 -0
- data/Unity/Library/EditorSettings.asset +0 -0
- data/Unity/Library/EditorUserBuildSettings.asset +0 -0
- data/Unity/Library/EditorUserSettings.asset +0 -0
- data/Unity/Library/InputManager.asset +0 -0
- data/Unity/Library/InspectorExpandedItems.asset +0 -0
- data/Unity/Library/MonoManager.asset +0 -0
- data/Unity/Library/NetworkManager.asset +0 -0
- data/Unity/Library/ProjectSettings.asset +0 -0
- data/Unity/Library/QualitySettings.asset +0 -0
- data/Unity/Library/TagManager.asset +0 -0
- data/Unity/Library/TimeManager.asset +0 -0
- data/Unity/ProjectSettings/AudioManager.asset +0 -0
- data/Unity/ProjectSettings/DynamicsManager.asset +0 -0
- data/Unity/ProjectSettings/EditorBuildSettings.asset +0 -0
- data/Unity/ProjectSettings/EditorSettings.asset +0 -0
- data/Unity/ProjectSettings/InputManager.asset +0 -0
- data/Unity/ProjectSettings/NavMeshLayers.asset +0 -0
- data/Unity/ProjectSettings/NetworkManager.asset +0 -0
- data/Unity/ProjectSettings/ProjectSettings.asset +0 -0
- data/Unity/ProjectSettings/QualitySettings.asset +0 -0
- data/Unity/ProjectSettings/TagManager.asset +0 -0
- data/Unity/ProjectSettings/TimeManager.asset +0 -0
- data/VERSION +1 -0
- data/bin/cukunity +7 -0
- data/cukunity.gemspec +298 -0
- data/features/get_scene_command.feature +39 -0
- data/features/i18n/ja/sokoban_clear.feature +109 -0
- data/features/install.feature +23 -0
- data/features/keyboard_input.feature +26 -0
- data/features/load_level_command.feature +36 -0
- data/features/run.feature +33 -0
- data/features/select_visible.feature +29 -0
- data/features/support/env.rb +11 -0
- data/features/touch_input.feature +96 -0
- data/lib/cukunity/cli/argument_parser.rb +103 -0
- data/lib/cukunity/cli/bootstrap_command.rb +39 -0
- data/lib/cukunity/cli/doctor_command.rb +150 -0
- data/lib/cukunity/cli/features_command.rb +35 -0
- data/lib/cukunity/cli/main.rb +18 -0
- data/lib/cukunity/cli/options.rb +8 -0
- data/lib/cukunity/cli.rb +11 -0
- data/lib/cukunity/cucumber/step_definitions/get_scene_command_steps.rb +37 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/get_scene_command_steps.rb +37 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/install_steps.rb +25 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/keyboard_input_steps.rb +17 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/load_level_command_steps.rb +17 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/run_steps.rb +25 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/select_visible_steps.rb +9 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja/touch_input_steps.rb +19 -0
- data/lib/cukunity/cucumber/step_definitions/i18n/ja.rb +9 -0
- data/lib/cukunity/cucumber/step_definitions/install_steps.rb +33 -0
- data/lib/cukunity/cucumber/step_definitions/keyboard_input_steps.rb +13 -0
- data/lib/cukunity/cucumber/step_definitions/load_level_command_steps.rb +17 -0
- data/lib/cukunity/cucumber/step_definitions/run_steps.rb +32 -0
- data/lib/cukunity/cucumber/step_definitions/select_visible_steps.rb +15 -0
- data/lib/cukunity/cucumber/step_definitions/touch_input_steps.rb +18 -0
- data/lib/cukunity/cucumber/support/platform_helpers.rb +105 -0
- data/lib/cukunity/cucumber/support/spec_helpers.rb +21 -0
- data/lib/cukunity/cucumber/support/transforms.rb +5 -0
- data/lib/cukunity/cucumber/support/unity_helpers.rb +35 -0
- data/lib/cukunity/cucumber.rb +28 -0
- data/lib/cukunity/drivers/android/app.rb +43 -0
- data/lib/cukunity/drivers/android/appkiller/.gitignore +9 -0
- data/lib/cukunity/drivers/android/appkiller/AndroidManifest.xml +17 -0
- data/lib/cukunity/drivers/android/appkiller/ant.properties +17 -0
- data/lib/cukunity/drivers/android/appkiller/build.xml +85 -0
- data/lib/cukunity/drivers/android/appkiller/proguard.cfg +40 -0
- data/lib/cukunity/drivers/android/appkiller/project.properties +11 -0
- data/lib/cukunity/drivers/android/appkiller/res/layout/main.xml +13 -0
- data/lib/cukunity/drivers/android/appkiller/res/values/strings.xml +4 -0
- data/lib/cukunity/drivers/android/appkiller/src/cukunity/appkiller/AppKiller.java +29 -0
- data/lib/cukunity/drivers/android/keyboard_input.rb +149 -0
- data/lib/cukunity/drivers/android/keycodes.rb +531 -0
- data/lib/cukunity/drivers/android/monkey_client.rb +65 -0
- data/lib/cukunity/drivers/android/process.rb +103 -0
- data/lib/cukunity/drivers/android/touch_input.rb +24 -0
- data/lib/cukunity/drivers/android/tunnel_client_methods.rb +22 -0
- data/lib/cukunity/drivers/android/unity_client.rb +27 -0
- data/lib/cukunity/drivers/android/utils.rb +50 -0
- data/lib/cukunity/drivers/android.rb +9 -0
- data/lib/cukunity/drivers/iOS/app.rb +32 -0
- data/lib/cukunity/drivers/iOS/keyboard_input.rb +104 -0
- data/lib/cukunity/drivers/iOS/process.rb +42 -0
- data/lib/cukunity/drivers/iOS/touch_input.rb +18 -0
- data/lib/cukunity/drivers/iOS/tunnel_client_methods.rb +27 -0
- data/lib/cukunity/drivers/iOS/uiautomation.rb +97 -0
- data/lib/cukunity/drivers/iOS/uiautomation_master.rb +152 -0
- data/lib/cukunity/drivers/iOS/uiautomation_slave.js +238 -0
- data/lib/cukunity/drivers/iOS/uiautomation_slave.rb +41 -0
- data/lib/cukunity/drivers/iOS/unity_client.rb +27 -0
- data/lib/cukunity/drivers/iOS/utils.rb +46 -0
- data/lib/cukunity/drivers/iOS.rb +9 -0
- data/lib/cukunity/exceptions.rb +24 -0
- data/lib/cukunity/keyboard_input_methods.rb +35 -0
- data/lib/cukunity/request_line_response_client_methods.rb +80 -0
- data/lib/cukunity/tasks/cucumber.rake +22 -0
- data/lib/cukunity/touch_input_methods.rb +19 -0
- data/lib/cukunity/unity/client_methods.rb +46 -0
- data/lib/cukunity/unity/command_facade.rb +67 -0
- data/lib/cukunity/unity/component.rb +39 -0
- data/lib/cukunity/unity/gameobject.rb +53 -0
- data/lib/cukunity/unity/hint.rb +27 -0
- data/lib/cukunity/unity/json_container.rb +48 -0
- data/lib/cukunity/unity/level.rb +6 -0
- data/lib/cukunity/unity/scene.rb +18 -0
- data/lib/cukunity/unity/screen.rb +45 -0
- data/lib/cukunity/unity/screen_hint_methods.rb +61 -0
- data/lib/cukunity/unity/text_hint_methods.rb +17 -0
- data/lib/cukunity/unity.rb +11 -0
- data/lib/cukunity/utils.rb +56 -0
- data/lib/cukunity.rb +19 -0
- metadata +411 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'socket'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'thread'
|
|
5
|
+
|
|
6
|
+
module Cukunity
|
|
7
|
+
module IOS
|
|
8
|
+
class UIAutomationMaster
|
|
9
|
+
include Singleton
|
|
10
|
+
include Cukunity::Utils
|
|
11
|
+
|
|
12
|
+
DEFAULT_HOSTNAME = '127.0.0.1'
|
|
13
|
+
DEFAULT_PORT = 9927
|
|
14
|
+
UIAUTOMATION_SLAVE_DEFAULT_TIMEOUT = 30
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
@server = nil
|
|
18
|
+
@client = nil
|
|
19
|
+
@client_mutex = Mutex.new
|
|
20
|
+
@client_acquired = ConditionVariable.new
|
|
21
|
+
@client_finished = ConditionVariable.new
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def command(name, options = {})
|
|
25
|
+
options = merge_options(options, { :max_time => UIAUTOMATION_SLAVE_DEFAULT_TIMEOUT })
|
|
26
|
+
max_time = options.delete(:max_time)
|
|
27
|
+
listen
|
|
28
|
+
begin
|
|
29
|
+
client = sync_with_client(max_time)
|
|
30
|
+
# write command to client
|
|
31
|
+
req = options.merge({'command' => name})
|
|
32
|
+
client.puts req.to_json
|
|
33
|
+
close_client(client)
|
|
34
|
+
client = nil
|
|
35
|
+
client = wait_client(max_time)
|
|
36
|
+
# wait response from client
|
|
37
|
+
res = JSON.parse(client.readline.chomp)
|
|
38
|
+
if res.nil? or res.has_key?('error')
|
|
39
|
+
raise Exception::UIAutomationSlaveInvalidResponse.new(res['error'])
|
|
40
|
+
end
|
|
41
|
+
res
|
|
42
|
+
rescue Errno::ECONNRESET
|
|
43
|
+
close_client(client)
|
|
44
|
+
sleep 0.1
|
|
45
|
+
retry
|
|
46
|
+
rescue Errno::EPIPE
|
|
47
|
+
close_client(client)
|
|
48
|
+
sleep 0.1
|
|
49
|
+
retry
|
|
50
|
+
ensure
|
|
51
|
+
close_client(client)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def address
|
|
56
|
+
listen
|
|
57
|
+
@server.local_address.ip_address
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def port
|
|
61
|
+
listen
|
|
62
|
+
@server.local_address.ip_port
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def close
|
|
66
|
+
unless @server.nil?
|
|
67
|
+
@server.close rescue ::Exception
|
|
68
|
+
@server = nil
|
|
69
|
+
end
|
|
70
|
+
unless @client_mutex.nil?
|
|
71
|
+
@client_mutex.synchronize do
|
|
72
|
+
@client_finished.signal
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
unless @thread.nil?
|
|
76
|
+
@thread.join
|
|
77
|
+
@thread = nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
def listen
|
|
83
|
+
return unless @server.nil?
|
|
84
|
+
# create server
|
|
85
|
+
@server = TCPServer.new(DEFAULT_HOSTNAME, DEFAULT_PORT)
|
|
86
|
+
# create a thread for dealing with clients
|
|
87
|
+
@thread = Thread.start do
|
|
88
|
+
begin
|
|
89
|
+
# keep accepting clients
|
|
90
|
+
loop do
|
|
91
|
+
break if @server.nil?
|
|
92
|
+
# new client
|
|
93
|
+
client = @server.accept
|
|
94
|
+
@client_mutex.synchronize do
|
|
95
|
+
@client = client
|
|
96
|
+
# inform others we got a client.
|
|
97
|
+
@client_acquired.signal
|
|
98
|
+
# wait for reply saying we don't need the client anymore
|
|
99
|
+
@client_finished.wait(@client_mutex)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
rescue Errno::EBADF
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def wait_client(max_time)
|
|
108
|
+
client = nil
|
|
109
|
+
@client_mutex.synchronize do
|
|
110
|
+
if @client.nil?
|
|
111
|
+
@client_acquired.wait(@client_mutex, max_time)
|
|
112
|
+
end
|
|
113
|
+
client = @client
|
|
114
|
+
end
|
|
115
|
+
raise Exception::UIAutomationSlaveTimeout.new if client.nil?
|
|
116
|
+
client
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def close_client(client)
|
|
120
|
+
@client_mutex.synchronize do
|
|
121
|
+
if !client.nil? and client == @client
|
|
122
|
+
@client = nil
|
|
123
|
+
client.close rescue ::Exception
|
|
124
|
+
@client_finished.signal
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def sync_with_client(max_time)
|
|
130
|
+
# wait for client
|
|
131
|
+
client = wait_client(max_time)
|
|
132
|
+
begin
|
|
133
|
+
# expect client ack
|
|
134
|
+
JSON.parse(client.readline.chomp).tap do |ack|
|
|
135
|
+
break unless ack != {}
|
|
136
|
+
# try to synchronize once again
|
|
137
|
+
close_client(client)
|
|
138
|
+
client = nil
|
|
139
|
+
client = wait_client(max_time)
|
|
140
|
+
JSON.parse(client.readline.chomp).tap do |ack|
|
|
141
|
+
raise Exception::UIAutomationSlaveInvalidAck.new if ack != {}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
rescue ::Exception => err
|
|
145
|
+
client_close(client)
|
|
146
|
+
raise err
|
|
147
|
+
end
|
|
148
|
+
client
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
UIAutomationSlave = function(address, port) {
|
|
2
|
+
this.address = address;
|
|
3
|
+
this.port = port;
|
|
4
|
+
|
|
5
|
+
this.target = UIATarget.localTarget();
|
|
6
|
+
this.target.setTimeout(0);
|
|
7
|
+
this.app = this.target.frontMostApp();
|
|
8
|
+
this.host = this.target.host();
|
|
9
|
+
|
|
10
|
+
this.result = {};
|
|
11
|
+
this.running = true;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
UIAutomationSlave.prototype.exchange = function(timeout) {
|
|
15
|
+
var ruby, args = [], request;
|
|
16
|
+
|
|
17
|
+
// prepare arguments to inform master of the result
|
|
18
|
+
ruby = "{{ruby}}";
|
|
19
|
+
args.push("{{slave}}");
|
|
20
|
+
|
|
21
|
+
args.push(this.address);
|
|
22
|
+
args.push(this.port.toString());
|
|
23
|
+
args.push(JSON.stringify(this.result));
|
|
24
|
+
|
|
25
|
+
timeout = timeout || 10;
|
|
26
|
+
|
|
27
|
+
// inform master of result and get a new request
|
|
28
|
+
try {
|
|
29
|
+
request = this.host.performTaskWithPathArgumentsTimeout(ruby, args, timeout);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
// timeout
|
|
33
|
+
return this.running;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
switch (request.exitCode) {
|
|
37
|
+
case 0:
|
|
38
|
+
try {
|
|
39
|
+
this.result = this.execute(JSON.parse(request.stdout));
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
this.result = { "error" : e.message };
|
|
43
|
+
}
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
// connection refused
|
|
47
|
+
case 2:
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 1:
|
|
51
|
+
default:
|
|
52
|
+
UIALogger.logError("Could not connect to master: " + request.exitCode);
|
|
53
|
+
this.result = {};
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
return this.running;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
UIAutomationSlave.prototype.keyboard = function() {
|
|
60
|
+
var keyboard = this.app.keyboard();
|
|
61
|
+
if ((keyboard) && (keyboard.isValid()) && (keyboard.isVisible())) {
|
|
62
|
+
return keyboard;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
UIAutomationSlave.prototype.handleKeyboard = function(result, success, error) {
|
|
68
|
+
var keyboard = this.keyboard();
|
|
69
|
+
if (keyboard) {
|
|
70
|
+
return success(keyboard);
|
|
71
|
+
}
|
|
72
|
+
result["error"] = "Keyboard currently unavailable";
|
|
73
|
+
if (error) {
|
|
74
|
+
return error();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
UIAutomationSlave.prototype.keyboardToolbar = function() {
|
|
79
|
+
var keyboardToolbar;
|
|
80
|
+
if (this.keyboard()) {
|
|
81
|
+
keyboardToolbar = this.app.mainWindow().toolbar();
|
|
82
|
+
if ((keyboardToolbar) && (keyboardToolbar.isValid()) &&
|
|
83
|
+
(keyboardToolbar.isVisible())) {
|
|
84
|
+
return keyboardToolbar;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
UIAutomationSlave.prototype.handleKeyboardToolbar = function(result, success, error) {
|
|
91
|
+
var keyboardToolbar = this.keyboardToolbar();
|
|
92
|
+
if (keyboardToolbar) {
|
|
93
|
+
return success(keyboardToolbar);
|
|
94
|
+
}
|
|
95
|
+
result["error"] = "Toolbar currently unavailable";
|
|
96
|
+
if (error) {
|
|
97
|
+
return error();
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
UIAutomationSlave.prototype.execute = function(request) {
|
|
102
|
+
var command = request['command'], result = { 'command': command };
|
|
103
|
+
|
|
104
|
+
// delete command from hash, so we can use the hash for passing into functions
|
|
105
|
+
delete request['command'];
|
|
106
|
+
|
|
107
|
+
// execute respective command
|
|
108
|
+
switch (command) {
|
|
109
|
+
case 'isKeyboardAvailable':
|
|
110
|
+
if (this.keyboard()) {
|
|
111
|
+
result["available"] = true;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
result["available"] = false;
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
117
|
+
|
|
118
|
+
case 'typedText':
|
|
119
|
+
this.handleKeyboardToolbar(result, function(keyboardToolbar) {
|
|
120
|
+
var textField = keyboardToolbar.textFields().toArray()[0];
|
|
121
|
+
if ((textField) && (textField.isValid())) {
|
|
122
|
+
result["text"] = textField.value();
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
result["error"] = "Text field not currently unavailable";
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'clearText':
|
|
131
|
+
this.handleKeyboardToolbar(result, function(keyboardToolbar) {
|
|
132
|
+
var textField = keyboardToolbar.textFields().toArray()[0];
|
|
133
|
+
if ((textField) && (textField.isValid())) {
|
|
134
|
+
textField.setValue('');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
result["error"] = "Text field not currently unavailable";
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
break;
|
|
141
|
+
|
|
142
|
+
case 'typeText':
|
|
143
|
+
this.handleKeyboard(result, function(keyboard) {
|
|
144
|
+
keyboard.typeString(request["text"]);
|
|
145
|
+
});
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case 'keyboardButtons':
|
|
149
|
+
this.handleKeyboard(result, function(keyboard) {
|
|
150
|
+
var buttons = [], button, _buttons = keyboard.buttons().toArray();
|
|
151
|
+
for (button in _buttons) {
|
|
152
|
+
buttons[button] = _buttons[button].name();
|
|
153
|
+
}
|
|
154
|
+
result["keyboardButtons"] = buttons;
|
|
155
|
+
});
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'tapKeyboardButton':
|
|
159
|
+
this.handleKeyboard(result, function(keyboard) {
|
|
160
|
+
var button = keyboard.buttons()[request["button"].toString()];
|
|
161
|
+
if ((button) && (button.isValid())) {
|
|
162
|
+
button.tap();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
result["error"] = "Button currently unavailable";
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
break;
|
|
169
|
+
|
|
170
|
+
case 'keyboardKeys':
|
|
171
|
+
this.handleKeyboard(result, function(keyboard) {
|
|
172
|
+
var key, keys = [], _keys = keyboard.keys().toArray();
|
|
173
|
+
for (key in _keys) {
|
|
174
|
+
keys[key] = _keys[key].name();
|
|
175
|
+
}
|
|
176
|
+
result["keyboardKeys"] = keys;
|
|
177
|
+
});
|
|
178
|
+
break;
|
|
179
|
+
|
|
180
|
+
case 'tapKeyboardKey':
|
|
181
|
+
this.handleKeyboard(result, function(keyboard) {
|
|
182
|
+
var key = keyboard.keys()[request["key"].toString()];
|
|
183
|
+
if ((key) && (key.isValid())) {
|
|
184
|
+
key.tap();
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
result["error"] = "Key currently unavailable";
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'keyboardToolbarButtons':
|
|
193
|
+
this.handleKeyboardToolbar(result, function(keyboardToolbar) {
|
|
194
|
+
var buttons = [], button, _buttons = keyboardToolbar.buttons().toArray();
|
|
195
|
+
for (button in _buttons) {
|
|
196
|
+
buttons[button] = _buttons[button].name();
|
|
197
|
+
}
|
|
198
|
+
result["keyboardToolbarButtons"] = buttons;
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
|
|
202
|
+
case 'tapKeyboardToolbarButton':
|
|
203
|
+
this.handleKeyboardToolbar(result, function(keyboardToolbar) {
|
|
204
|
+
var button = keyboardToolbar.buttons()[request["button"].toString()];
|
|
205
|
+
if ((button) && (button.isValid())) {
|
|
206
|
+
button.tap();
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
result["error"] = "Button currently unavailable";
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
break;
|
|
213
|
+
|
|
214
|
+
case 'bundleIdentifier':
|
|
215
|
+
result['bundleIdentifier'] = "{{bundle_id}}";
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case 'terminate':
|
|
219
|
+
this.running = false;
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
default:
|
|
223
|
+
result["error"] = "Unknown command";
|
|
224
|
+
break;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return result;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
(function() {
|
|
231
|
+
// create automation slave
|
|
232
|
+
var slave = new UIAutomationSlave("{{address}}", {{port}});
|
|
233
|
+
|
|
234
|
+
// execute happily
|
|
235
|
+
while (slave.exchange()) {
|
|
236
|
+
// don't need to wait, it's already slow enough...
|
|
237
|
+
}
|
|
238
|
+
})();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require 'singleton'
|
|
2
|
+
require 'socket'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Cukunity
|
|
6
|
+
module IOS
|
|
7
|
+
class UIAutomationSlave
|
|
8
|
+
def exchange(host, port, result = {})
|
|
9
|
+
sock = TCPSocket.new(host, port)
|
|
10
|
+
begin
|
|
11
|
+
sock.puts result.to_json
|
|
12
|
+
JSON.parse(sock.gets.chomp)
|
|
13
|
+
ensure
|
|
14
|
+
sock.close unless sock.nil?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if __FILE__ == $0
|
|
22
|
+
begin
|
|
23
|
+
# read commands (no error handling)
|
|
24
|
+
host = ARGV.shift
|
|
25
|
+
port = ARGV.shift.to_i
|
|
26
|
+
result = JSON.parse(ARGV.shift)
|
|
27
|
+
# create client
|
|
28
|
+
slave = Cukunity::IOS::UIAutomationSlave.new
|
|
29
|
+
# send result of previous command and wait for new
|
|
30
|
+
command = slave.exchange(host, port, result).to_json
|
|
31
|
+
# print out new command
|
|
32
|
+
puts command
|
|
33
|
+
exit(0)
|
|
34
|
+
rescue Errno::ECONNREFUSED => err
|
|
35
|
+
$stderr.puts "Error: #{err}"
|
|
36
|
+
exit(2)
|
|
37
|
+
rescue => err
|
|
38
|
+
$stderr.puts "Error: #{err}"
|
|
39
|
+
exit(1)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Cukunity
|
|
2
|
+
module IOS
|
|
3
|
+
module Unity
|
|
4
|
+
class Client
|
|
5
|
+
include Cukunity::IOS::TunnelClientMethods
|
|
6
|
+
include Cukunity::Unity::ClientMethods
|
|
7
|
+
alias_method :client_connect_original, :connect
|
|
8
|
+
alias_method :client_close_original, :close
|
|
9
|
+
|
|
10
|
+
def platform
|
|
11
|
+
Cukunity::IOS
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def close
|
|
15
|
+
client_close_original
|
|
16
|
+
close_tunnel
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def connect(host = DEFAULT_HOSTNAME, port = DEFAULT_PORT)
|
|
21
|
+
lport, rport = open_tunnel(0, port)
|
|
22
|
+
client_connect_original(host, lport)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Cukunity
|
|
2
|
+
module IOS
|
|
3
|
+
module Utils
|
|
4
|
+
include Cukunity::Utils
|
|
5
|
+
|
|
6
|
+
MOBILE_DEVICE_DEFAULT_TIMEOUT = 60
|
|
7
|
+
|
|
8
|
+
def unity_command(name, opts = {})
|
|
9
|
+
Cukunity::IOS::Unity::Client.instance.command(name, opts)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def mobile_device_cmd(cmd, options = {})
|
|
13
|
+
options = merge_options(options, { :wait => true, :max_time => MOBILE_DEVICE_DEFAULT_TIMEOUT })
|
|
14
|
+
pipe = IO.popen(['mobiledevice'] + cmd)
|
|
15
|
+
return pipe unless options[:wait]
|
|
16
|
+
status = nil
|
|
17
|
+
raise Exception::MobileDeviceTimeout.new(cmd) if check_timeout(options[:max_time]) do
|
|
18
|
+
if ::Process.waitpid(pipe.pid, ::Process::WNOHANG)
|
|
19
|
+
status = $?
|
|
20
|
+
true
|
|
21
|
+
else
|
|
22
|
+
false
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
output = pipe.readlines
|
|
26
|
+
pipe.close
|
|
27
|
+
raise Exception::MobileDeviceError.new(cmd) unless status.exitstatus == 0
|
|
28
|
+
output
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def device_udid
|
|
32
|
+
udid = mobile_device_cmd(['get_udid']).first.chomp
|
|
33
|
+
raise Exception::MobileDeviceError.new(udid) unless udid.length == 40
|
|
34
|
+
udid
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def bundle_identifier(app_folder)
|
|
38
|
+
mobile_device_cmd(['get_bundle_id', app_folder]).first.chomp
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def uiautomation_cmd(name, options = {})
|
|
42
|
+
Cukunity::IOS::UIAutomationMaster.instance.command(name, options)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
require 'cukunity/drivers/iOS/utils'
|
|
2
|
+
require 'cukunity/drivers/iOS/uiautomation'
|
|
3
|
+
require 'cukunity/drivers/iOS/uiautomation_master'
|
|
4
|
+
require 'cukunity/drivers/iOS/tunnel_client_methods'
|
|
5
|
+
require 'cukunity/drivers/iOS/app'
|
|
6
|
+
require 'cukunity/drivers/iOS/process'
|
|
7
|
+
require 'cukunity/drivers/iOS/touch_input'
|
|
8
|
+
require 'cukunity/drivers/iOS/keyboard_input'
|
|
9
|
+
require 'cukunity/drivers/iOS/unity_client'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Cukunity
|
|
2
|
+
module Exception
|
|
3
|
+
class UnspecifiedPlatform < ::Exception; end
|
|
4
|
+
class InvalidApp < ::Exception; end
|
|
5
|
+
class LaunchTimeout < ::Exception; end
|
|
6
|
+
class InstallFailed < ::Exception; end
|
|
7
|
+
class UninstallFailed < ::Exception; end
|
|
8
|
+
class InstallTimeout < ::Exception; end
|
|
9
|
+
class UninstallTimeout < ::Exception; end
|
|
10
|
+
class TunnelError < ::Exception; end
|
|
11
|
+
class ConnectionTimeout < ::Exception; end
|
|
12
|
+
class MonkeyInvalidAck < ::Exception; end
|
|
13
|
+
class MonkeyCommandError < ::Exception; end
|
|
14
|
+
class MonkeyConnectTimeout < ::Exception; end
|
|
15
|
+
class MobileDeviceError < ::Exception; end
|
|
16
|
+
class MobileDeviceTimeout < ::Exception; end
|
|
17
|
+
class UIAutomationSlaveTimeout < ::Exception; end
|
|
18
|
+
class UIAutomationSlaveInvalidAck < ::Exception; end
|
|
19
|
+
class UIAutomationSlaveInvalidResponse < ::Exception; end
|
|
20
|
+
class KeyboardWaitTimeout < ::Exception; end
|
|
21
|
+
class UnityInvalidAck < ::Exception; end
|
|
22
|
+
class UnityCommandError < ::Exception; end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Cukunity
|
|
2
|
+
module KeyboardInputMethods
|
|
3
|
+
include Cukunity::Utils
|
|
4
|
+
|
|
5
|
+
def open(options = {})
|
|
6
|
+
options = merge_options(options, { :clear => false, :close => true })
|
|
7
|
+
platform_open_keyboard(restrict_options(options, :clear))
|
|
8
|
+
yield self
|
|
9
|
+
if options[:close]
|
|
10
|
+
close_keyboard
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def keyboard_command(options)
|
|
16
|
+
options = to_options(options)
|
|
17
|
+
wait = (options.delete(:wait) || 0.5).to_f
|
|
18
|
+
cmd = yield
|
|
19
|
+
res = platform_keyboard_command(options, *cmd)
|
|
20
|
+
sleep(wait) unless wait.nil? or wait <= 0
|
|
21
|
+
res
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def close_keyboard(options = {})
|
|
25
|
+
raise Exception::KeyboardWaitTimeout.new if check_timeout(10) do
|
|
26
|
+
if closed?
|
|
27
|
+
true
|
|
28
|
+
else
|
|
29
|
+
press_back
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
module Cukunity
|
|
4
|
+
module RequestLineResponseClientMethods
|
|
5
|
+
include Cukunity::Utils
|
|
6
|
+
|
|
7
|
+
DEFAULT_TIMEOUT = 30
|
|
8
|
+
|
|
9
|
+
def request(line, options = {})
|
|
10
|
+
options = merge_options(options, { :max_time => DEFAULT_TIMEOUT,
|
|
11
|
+
:quit => false, :retry => true })
|
|
12
|
+
connect
|
|
13
|
+
response = nil
|
|
14
|
+
begin
|
|
15
|
+
raise Exception::MobileDeviceTimeout.new(cmd) if check_timeout(options[:max_time]) do
|
|
16
|
+
@socket.puts(line)
|
|
17
|
+
@socket.flush
|
|
18
|
+
return if options[:quit]
|
|
19
|
+
response = @socket.gets
|
|
20
|
+
break unless response.nil? and options[:retry]
|
|
21
|
+
close
|
|
22
|
+
sleep 0.1
|
|
23
|
+
connect
|
|
24
|
+
false
|
|
25
|
+
end
|
|
26
|
+
rescue Errno::ECONNRESET
|
|
27
|
+
return if options[:quit]
|
|
28
|
+
close
|
|
29
|
+
if options[:retry]
|
|
30
|
+
connect
|
|
31
|
+
retry
|
|
32
|
+
end
|
|
33
|
+
rescue Errno::EPIPE
|
|
34
|
+
return if options[:quit]
|
|
35
|
+
close
|
|
36
|
+
if options[:retry]
|
|
37
|
+
connect
|
|
38
|
+
retry
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
if block_given?
|
|
42
|
+
yield response
|
|
43
|
+
else
|
|
44
|
+
response
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
def connect(host, port, max_time = DEFAULT_TIMEOUT)
|
|
50
|
+
return unless @socket.nil? or @socket.closed?
|
|
51
|
+
raise Exception::ConnectionTimeout.new if check_timeout(max_time) do
|
|
52
|
+
# new connection to server
|
|
53
|
+
@socket = TCPSocket.new(host, port)
|
|
54
|
+
# get ACK from server
|
|
55
|
+
if block_given?
|
|
56
|
+
begin
|
|
57
|
+
yield @socket
|
|
58
|
+
rescue
|
|
59
|
+
# close and retry
|
|
60
|
+
@socket.close rescue ::Exception
|
|
61
|
+
@socket = nil
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def connected?
|
|
70
|
+
!@socket.nil?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def close
|
|
74
|
+
unless @socket.nil?
|
|
75
|
+
@socket.close rescue ::Exception
|
|
76
|
+
@socket = nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Cucumber::Rake::Task.new(:android) do |features|
|
|
2
|
+
features.cucumber_opts = "features --format progress -t ~@ios,@android"
|
|
3
|
+
end
|
|
4
|
+
namespace :android do
|
|
5
|
+
Cucumber::Rake::Task.new(:pretty) do |features|
|
|
6
|
+
features.cucumber_opts = "features --format pretty -t ~@ios,@android"
|
|
7
|
+
end
|
|
8
|
+
Cucumber::Rake::Task.new(:wip) do |features|
|
|
9
|
+
features.cucumber_opts = "features --format pretty -t ~@ios,@android -t @wip --wip"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
Cucumber::Rake::Task.new(:ios) do |features|
|
|
13
|
+
features.cucumber_opts = "features --format progress -t ~@android,@ios"
|
|
14
|
+
end
|
|
15
|
+
namespace :ios do
|
|
16
|
+
Cucumber::Rake::Task.new(:pretty) do |features|
|
|
17
|
+
features.cucumber_opts = "features --format pretty -t ~@android,@ios"
|
|
18
|
+
end
|
|
19
|
+
Cucumber::Rake::Task.new(:wip) do |features|
|
|
20
|
+
features.cucumber_opts = "features --format pretty -t ~@android,@ios -t @wip --wip"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Cukunity
|
|
2
|
+
module TouchInputMethods
|
|
3
|
+
include Cukunity::Utils
|
|
4
|
+
|
|
5
|
+
DEFAULT_TAP_DELAY = 0.5
|
|
6
|
+
|
|
7
|
+
def default_tap_delay=(delay)
|
|
8
|
+
@default_tap_delay = delay.to_f
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def set_default_tap_delay(delay)
|
|
12
|
+
default_tap_delay = delay
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def default_tap_delay
|
|
16
|
+
@default_tap_delay || DEFAULT_TAP_DELAY
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|