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.
Files changed (226) hide show
  1. data/.document +5 -0
  2. data/.gitmodules +0 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +60 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +214 -0
  8. data/Rakefile +51 -0
  9. data/Unity/Assets/Demo/Art/Buttons/down.png +0 -0
  10. data/Unity/Assets/Demo/Art/Buttons/down.png.meta +28 -0
  11. data/Unity/Assets/Demo/Art/Buttons/left.png +0 -0
  12. data/Unity/Assets/Demo/Art/Buttons/left.png.meta +28 -0
  13. data/Unity/Assets/Demo/Art/Buttons/ok.png +0 -0
  14. data/Unity/Assets/Demo/Art/Buttons/ok.png.meta +28 -0
  15. data/Unity/Assets/Demo/Art/Buttons/options.png +0 -0
  16. data/Unity/Assets/Demo/Art/Buttons/options.png.meta +28 -0
  17. data/Unity/Assets/Demo/Art/Buttons/play.png +0 -0
  18. data/Unity/Assets/Demo/Art/Buttons/play.png.meta +28 -0
  19. data/Unity/Assets/Demo/Art/Buttons/quit.png +0 -0
  20. data/Unity/Assets/Demo/Art/Buttons/quit.png.meta +28 -0
  21. data/Unity/Assets/Demo/Art/Buttons/retry.png +0 -0
  22. data/Unity/Assets/Demo/Art/Buttons/retry.png.meta +28 -0
  23. data/Unity/Assets/Demo/Art/Buttons/right.png +0 -0
  24. data/Unity/Assets/Demo/Art/Buttons/right.png.meta +28 -0
  25. data/Unity/Assets/Demo/Art/Buttons/up.png +0 -0
  26. data/Unity/Assets/Demo/Art/Buttons/up.png.meta +28 -0
  27. data/Unity/Assets/Demo/Art/Buttons.meta +2 -0
  28. data/Unity/Assets/Demo/Art/clear.png +0 -0
  29. data/Unity/Assets/Demo/Art/clear.png.meta +28 -0
  30. data/Unity/Assets/Demo/Art/options.png +0 -0
  31. data/Unity/Assets/Demo/Art/options.png.meta +28 -0
  32. data/Unity/Assets/Demo/Art/sokoban.png +0 -0
  33. data/Unity/Assets/Demo/Art/sokoban.png.meta +28 -0
  34. data/Unity/Assets/Demo/Art.meta +2 -0
  35. data/Unity/Assets/Demo/Cubes/CrateCube.mat +0 -0
  36. data/Unity/Assets/Demo/Cubes/CrateCube.mat.meta +2 -0
  37. data/Unity/Assets/Demo/Cubes/CrateCube.prefab +0 -0
  38. data/Unity/Assets/Demo/Cubes/CrateCube.prefab.meta +2 -0
  39. data/Unity/Assets/Demo/Cubes/FloorCube.mat +0 -0
  40. data/Unity/Assets/Demo/Cubes/FloorCube.mat.meta +2 -0
  41. data/Unity/Assets/Demo/Cubes/FloorCube.prefab +0 -0
  42. data/Unity/Assets/Demo/Cubes/FloorCube.prefab.meta +2 -0
  43. data/Unity/Assets/Demo/Cubes/GoalCube.mat +0 -0
  44. data/Unity/Assets/Demo/Cubes/GoalCube.mat.meta +2 -0
  45. data/Unity/Assets/Demo/Cubes/GoalCube.prefab +0 -0
  46. data/Unity/Assets/Demo/Cubes/GoalCube.prefab.meta +2 -0
  47. data/Unity/Assets/Demo/Cubes/PlayerCube.mat +0 -0
  48. data/Unity/Assets/Demo/Cubes/PlayerCube.mat.meta +2 -0
  49. data/Unity/Assets/Demo/Cubes/PlayerCube.prefab +0 -0
  50. data/Unity/Assets/Demo/Cubes/PlayerCube.prefab.meta +2 -0
  51. data/Unity/Assets/Demo/Cubes/WallCube.mat +0 -0
  52. data/Unity/Assets/Demo/Cubes/WallCube.mat.meta +2 -0
  53. data/Unity/Assets/Demo/Cubes/WallCube.prefab +0 -0
  54. data/Unity/Assets/Demo/Cubes/WallCube.prefab.meta +2 -0
  55. data/Unity/Assets/Demo/Cubes.meta +2 -0
  56. data/Unity/Assets/Demo/Resources/MainMenu.prefab +0 -0
  57. data/Unity/Assets/Demo/Resources/MainMenu.prefab.meta +2 -0
  58. data/Unity/Assets/Demo/Resources/OptionsMenu.prefab +0 -0
  59. data/Unity/Assets/Demo/Resources/OptionsMenu.prefab.meta +2 -0
  60. data/Unity/Assets/Demo/Resources.meta +2 -0
  61. data/Unity/Assets/Demo/Scripts/CameraController.js +18 -0
  62. data/Unity/Assets/Demo/Scripts/CameraController.js.meta +2 -0
  63. data/Unity/Assets/Demo/Scripts/EnterPlayerName.js +21 -0
  64. data/Unity/Assets/Demo/Scripts/EnterPlayerName.js.meta +2 -0
  65. data/Unity/Assets/Demo/Scripts/GameLeftButton.js +18 -0
  66. data/Unity/Assets/Demo/Scripts/GameLeftButton.js.meta +2 -0
  67. data/Unity/Assets/Demo/Scripts/GameQuitButton.js +6 -0
  68. data/Unity/Assets/Demo/Scripts/GameQuitButton.js.meta +2 -0
  69. data/Unity/Assets/Demo/Scripts/GameRetryButton.js +7 -0
  70. data/Unity/Assets/Demo/Scripts/GameRetryButton.js.meta +2 -0
  71. data/Unity/Assets/Demo/Scripts/GameRightButton.js +18 -0
  72. data/Unity/Assets/Demo/Scripts/GameRightButton.js.meta +2 -0
  73. data/Unity/Assets/Demo/Scripts/GameUpButton.js +18 -0
  74. data/Unity/Assets/Demo/Scripts/GameUpButton.js.meta +2 -0
  75. data/Unity/Assets/Demo/Scripts/OnTouchGUIElementDown.js +34 -0
  76. data/Unity/Assets/Demo/Scripts/OnTouchGUIElementDown.js.meta +2 -0
  77. data/Unity/Assets/Demo/Scripts/PlayerData.js +3 -0
  78. data/Unity/Assets/Demo/Scripts/PlayerData.js.meta +2 -0
  79. data/Unity/Assets/Demo/Scripts/SokobanController.js +240 -0
  80. data/Unity/Assets/Demo/Scripts/SokobanController.js.meta +2 -0
  81. data/Unity/Assets/Demo/Scripts/TakeScreenShot.js +26 -0
  82. data/Unity/Assets/Demo/Scripts/TakeScreenShot.js.meta +2 -0
  83. data/Unity/Assets/Demo/Scripts/TitleOptionsButton.js +8 -0
  84. data/Unity/Assets/Demo/Scripts/TitleOptionsButton.js.meta +2 -0
  85. data/Unity/Assets/Demo/Scripts/TitleOptionsOkButton.js +8 -0
  86. data/Unity/Assets/Demo/Scripts/TitleOptionsOkButton.js.meta +2 -0
  87. data/Unity/Assets/Demo/Scripts/TitlePlayButton.js +6 -0
  88. data/Unity/Assets/Demo/Scripts/TitlePlayButton.js.meta +2 -0
  89. data/Unity/Assets/Demo/Scripts/TitleQuitButton.js +7 -0
  90. data/Unity/Assets/Demo/Scripts/TitleQuitButton.js.meta +2 -0
  91. data/Unity/Assets/Demo/Scripts/UpdateMovesLabel.js +26 -0
  92. data/Unity/Assets/Demo/Scripts/UpdateMovesLabel.js.meta +2 -0
  93. data/Unity/Assets/Demo/Scripts/UpdatePlayerName.js +14 -0
  94. data/Unity/Assets/Demo/Scripts/UpdatePlayerName.js.meta +2 -0
  95. data/Unity/Assets/Demo/Scripts.meta +2 -0
  96. data/Unity/Assets/Demo/demo_game.unity +0 -0
  97. data/Unity/Assets/Demo/demo_game.unity.meta +2 -0
  98. data/Unity/Assets/Demo/demo_title.unity +0 -0
  99. data/Unity/Assets/Demo/demo_title.unity.meta +2 -0
  100. data/Unity/Assets/Demo.meta +2 -0
  101. data/Unity/Assets/Plugins/CukunityInstance.prefab +0 -0
  102. data/Unity/Assets/Plugins/CukunityInstance.prefab.meta +2 -0
  103. data/Unity/Assets/Plugins/CukunityServer.cs +871 -0
  104. data/Unity/Assets/Plugins/CukunityServer.cs.meta +2 -0
  105. data/Unity/Assets/Plugins/JsonFx.Json.dll +0 -0
  106. data/Unity/Assets/Plugins/JsonFx.Json.dll.meta +2 -0
  107. data/Unity/Assets/Plugins/iOS/CukunityServer.mm +242 -0
  108. data/Unity/Assets/Plugins/iOS/CukunityServer.mm.meta +2 -0
  109. data/Unity/Assets/Plugins/iOS.meta +2 -0
  110. data/Unity/Assets/Plugins.meta +2 -0
  111. data/Unity/Library/AudioManager.asset +0 -0
  112. data/Unity/Library/BuildSettings.asset +0 -0
  113. data/Unity/Library/DynamicsManager.asset +0 -0
  114. data/Unity/Library/EditorBuildSettings.asset +0 -0
  115. data/Unity/Library/EditorSettings.asset +0 -0
  116. data/Unity/Library/EditorUserBuildSettings.asset +0 -0
  117. data/Unity/Library/EditorUserSettings.asset +0 -0
  118. data/Unity/Library/InputManager.asset +0 -0
  119. data/Unity/Library/InspectorExpandedItems.asset +0 -0
  120. data/Unity/Library/MonoManager.asset +0 -0
  121. data/Unity/Library/NetworkManager.asset +0 -0
  122. data/Unity/Library/ProjectSettings.asset +0 -0
  123. data/Unity/Library/QualitySettings.asset +0 -0
  124. data/Unity/Library/TagManager.asset +0 -0
  125. data/Unity/Library/TimeManager.asset +0 -0
  126. data/Unity/ProjectSettings/AudioManager.asset +0 -0
  127. data/Unity/ProjectSettings/DynamicsManager.asset +0 -0
  128. data/Unity/ProjectSettings/EditorBuildSettings.asset +0 -0
  129. data/Unity/ProjectSettings/EditorSettings.asset +0 -0
  130. data/Unity/ProjectSettings/InputManager.asset +0 -0
  131. data/Unity/ProjectSettings/NavMeshLayers.asset +0 -0
  132. data/Unity/ProjectSettings/NetworkManager.asset +0 -0
  133. data/Unity/ProjectSettings/ProjectSettings.asset +0 -0
  134. data/Unity/ProjectSettings/QualitySettings.asset +0 -0
  135. data/Unity/ProjectSettings/TagManager.asset +0 -0
  136. data/Unity/ProjectSettings/TimeManager.asset +0 -0
  137. data/VERSION +1 -0
  138. data/bin/cukunity +7 -0
  139. data/cukunity.gemspec +298 -0
  140. data/features/get_scene_command.feature +39 -0
  141. data/features/i18n/ja/sokoban_clear.feature +109 -0
  142. data/features/install.feature +23 -0
  143. data/features/keyboard_input.feature +26 -0
  144. data/features/load_level_command.feature +36 -0
  145. data/features/run.feature +33 -0
  146. data/features/select_visible.feature +29 -0
  147. data/features/support/env.rb +11 -0
  148. data/features/touch_input.feature +96 -0
  149. data/lib/cukunity/cli/argument_parser.rb +103 -0
  150. data/lib/cukunity/cli/bootstrap_command.rb +39 -0
  151. data/lib/cukunity/cli/doctor_command.rb +150 -0
  152. data/lib/cukunity/cli/features_command.rb +35 -0
  153. data/lib/cukunity/cli/main.rb +18 -0
  154. data/lib/cukunity/cli/options.rb +8 -0
  155. data/lib/cukunity/cli.rb +11 -0
  156. data/lib/cukunity/cucumber/step_definitions/get_scene_command_steps.rb +37 -0
  157. data/lib/cukunity/cucumber/step_definitions/i18n/ja/get_scene_command_steps.rb +37 -0
  158. data/lib/cukunity/cucumber/step_definitions/i18n/ja/install_steps.rb +25 -0
  159. data/lib/cukunity/cucumber/step_definitions/i18n/ja/keyboard_input_steps.rb +17 -0
  160. data/lib/cukunity/cucumber/step_definitions/i18n/ja/load_level_command_steps.rb +17 -0
  161. data/lib/cukunity/cucumber/step_definitions/i18n/ja/run_steps.rb +25 -0
  162. data/lib/cukunity/cucumber/step_definitions/i18n/ja/select_visible_steps.rb +9 -0
  163. data/lib/cukunity/cucumber/step_definitions/i18n/ja/touch_input_steps.rb +19 -0
  164. data/lib/cukunity/cucumber/step_definitions/i18n/ja.rb +9 -0
  165. data/lib/cukunity/cucumber/step_definitions/install_steps.rb +33 -0
  166. data/lib/cukunity/cucumber/step_definitions/keyboard_input_steps.rb +13 -0
  167. data/lib/cukunity/cucumber/step_definitions/load_level_command_steps.rb +17 -0
  168. data/lib/cukunity/cucumber/step_definitions/run_steps.rb +32 -0
  169. data/lib/cukunity/cucumber/step_definitions/select_visible_steps.rb +15 -0
  170. data/lib/cukunity/cucumber/step_definitions/touch_input_steps.rb +18 -0
  171. data/lib/cukunity/cucumber/support/platform_helpers.rb +105 -0
  172. data/lib/cukunity/cucumber/support/spec_helpers.rb +21 -0
  173. data/lib/cukunity/cucumber/support/transforms.rb +5 -0
  174. data/lib/cukunity/cucumber/support/unity_helpers.rb +35 -0
  175. data/lib/cukunity/cucumber.rb +28 -0
  176. data/lib/cukunity/drivers/android/app.rb +43 -0
  177. data/lib/cukunity/drivers/android/appkiller/.gitignore +9 -0
  178. data/lib/cukunity/drivers/android/appkiller/AndroidManifest.xml +17 -0
  179. data/lib/cukunity/drivers/android/appkiller/ant.properties +17 -0
  180. data/lib/cukunity/drivers/android/appkiller/build.xml +85 -0
  181. data/lib/cukunity/drivers/android/appkiller/proguard.cfg +40 -0
  182. data/lib/cukunity/drivers/android/appkiller/project.properties +11 -0
  183. data/lib/cukunity/drivers/android/appkiller/res/layout/main.xml +13 -0
  184. data/lib/cukunity/drivers/android/appkiller/res/values/strings.xml +4 -0
  185. data/lib/cukunity/drivers/android/appkiller/src/cukunity/appkiller/AppKiller.java +29 -0
  186. data/lib/cukunity/drivers/android/keyboard_input.rb +149 -0
  187. data/lib/cukunity/drivers/android/keycodes.rb +531 -0
  188. data/lib/cukunity/drivers/android/monkey_client.rb +65 -0
  189. data/lib/cukunity/drivers/android/process.rb +103 -0
  190. data/lib/cukunity/drivers/android/touch_input.rb +24 -0
  191. data/lib/cukunity/drivers/android/tunnel_client_methods.rb +22 -0
  192. data/lib/cukunity/drivers/android/unity_client.rb +27 -0
  193. data/lib/cukunity/drivers/android/utils.rb +50 -0
  194. data/lib/cukunity/drivers/android.rb +9 -0
  195. data/lib/cukunity/drivers/iOS/app.rb +32 -0
  196. data/lib/cukunity/drivers/iOS/keyboard_input.rb +104 -0
  197. data/lib/cukunity/drivers/iOS/process.rb +42 -0
  198. data/lib/cukunity/drivers/iOS/touch_input.rb +18 -0
  199. data/lib/cukunity/drivers/iOS/tunnel_client_methods.rb +27 -0
  200. data/lib/cukunity/drivers/iOS/uiautomation.rb +97 -0
  201. data/lib/cukunity/drivers/iOS/uiautomation_master.rb +152 -0
  202. data/lib/cukunity/drivers/iOS/uiautomation_slave.js +238 -0
  203. data/lib/cukunity/drivers/iOS/uiautomation_slave.rb +41 -0
  204. data/lib/cukunity/drivers/iOS/unity_client.rb +27 -0
  205. data/lib/cukunity/drivers/iOS/utils.rb +46 -0
  206. data/lib/cukunity/drivers/iOS.rb +9 -0
  207. data/lib/cukunity/exceptions.rb +24 -0
  208. data/lib/cukunity/keyboard_input_methods.rb +35 -0
  209. data/lib/cukunity/request_line_response_client_methods.rb +80 -0
  210. data/lib/cukunity/tasks/cucumber.rake +22 -0
  211. data/lib/cukunity/touch_input_methods.rb +19 -0
  212. data/lib/cukunity/unity/client_methods.rb +46 -0
  213. data/lib/cukunity/unity/command_facade.rb +67 -0
  214. data/lib/cukunity/unity/component.rb +39 -0
  215. data/lib/cukunity/unity/gameobject.rb +53 -0
  216. data/lib/cukunity/unity/hint.rb +27 -0
  217. data/lib/cukunity/unity/json_container.rb +48 -0
  218. data/lib/cukunity/unity/level.rb +6 -0
  219. data/lib/cukunity/unity/scene.rb +18 -0
  220. data/lib/cukunity/unity/screen.rb +45 -0
  221. data/lib/cukunity/unity/screen_hint_methods.rb +61 -0
  222. data/lib/cukunity/unity/text_hint_methods.rb +17 -0
  223. data/lib/cukunity/unity.rb +11 -0
  224. data/lib/cukunity/utils.rb +56 -0
  225. data/lib/cukunity.rb +19 -0
  226. 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