cukunity 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|