kolach-melomel 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +35 -0
- data/README.md +58 -0
- data/lib/melomel.rb +48 -0
- data/lib/melomel/bridge.rb +76 -0
- data/lib/melomel/bridge/messaging.rb +252 -0
- data/lib/melomel/bridge/ui.rb +155 -0
- data/lib/melomel/cucumber.rb +150 -0
- data/lib/melomel/cucumber/alert_steps.rb +40 -0
- data/lib/melomel/cucumber/button_steps.rb +17 -0
- data/lib/melomel/cucumber/color_picker_steps.rb +18 -0
- data/lib/melomel/cucumber/data_grid_steps.rb +83 -0
- data/lib/melomel/cucumber/date_steps.rb +22 -0
- data/lib/melomel/cucumber/list_steps.rb +30 -0
- data/lib/melomel/cucumber/slider_steps.rb +20 -0
- data/lib/melomel/cucumber/text_steps.rb +26 -0
- data/lib/melomel/date.rb +19 -0
- data/lib/melomel/error.rb +43 -0
- data/lib/melomel/flex.rb +46 -0
- data/lib/melomel/object_proxy.rb +94 -0
- data/lib/melomel/version.rb +3 -0
- data/lib/object.rb +30 -0
- data/test/helper.rb +28 -0
- data/test/sandbox.rb +14 -0
- data/test/test_bridge.rb +61 -0
- data/test/test_integration.rb +71 -0
- data/test/test_messaging.rb +109 -0
- data/test/test_object_proxy.rb +43 -0
- data/test/test_ui.rb +59 -0
- metadata +148 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
v0.6.4
|
2
|
+
* added `I should see no data in the "([^"]*)" data grid'
|
3
|
+
|
4
|
+
v0.6.3
|
5
|
+
* minor changes in working with data grids
|
6
|
+
|
7
|
+
v0.6.2 contribution
|
8
|
+
* added Advanced Data Grid class support for grid steps
|
9
|
+
* minor issue fixed: nil.trim!
|
10
|
+
* added `should not see' for text label
|
11
|
+
|
12
|
+
v0.6.0
|
13
|
+
* Added wait interface.
|
14
|
+
* Added focus to components in Cucumber steps.
|
15
|
+
|
16
|
+
v0.5.0
|
17
|
+
* Added Cucumber support.
|
18
|
+
|
19
|
+
v0.4.0
|
20
|
+
* Added error serialization with stack tracing from Flash to Ruby.
|
21
|
+
|
22
|
+
|
23
|
+
v0.3.1
|
24
|
+
* Removed requirement for adding a bang to the end of no-arg methods.
|
25
|
+
|
26
|
+
v0.3.0
|
27
|
+
* Added `Melomel.invoke_function()` for package level functions invocation. (Nikita Dudnik)
|
28
|
+
|
29
|
+
v0.2.0
|
30
|
+
* Moved `Melomel::UI` methods into `Melomel`.
|
31
|
+
* Changed Flex build from `ant compile` to `ant build`.
|
32
|
+
* Made `build` the default ant target.
|
33
|
+
|
34
|
+
v0.1.0
|
35
|
+
* Initial alpha version.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
melomel.rb -- A Ruby interface to Melomel
|
2
|
+
=========================================
|
3
|
+
|
4
|
+
## DESCRIPTION
|
5
|
+
|
6
|
+
Melomel.rb is a library that allows Ruby to communicate with an application
|
7
|
+
running within the Flash virtual machine. For more information on Melomel,
|
8
|
+
visit the Melomel site:
|
9
|
+
|
10
|
+
http://melomel.info
|
11
|
+
|
12
|
+
Melomel.rb follows the rules of [Semantic Versioning](http://semver.org/) and
|
13
|
+
uses [TomDoc](http://tomdoc.org/) for inline documentation.
|
14
|
+
|
15
|
+
|
16
|
+
## INSTALLATION
|
17
|
+
|
18
|
+
To install Melomel.rb simply use RubyGems:
|
19
|
+
|
20
|
+
$ [sudo] gem install melomel
|
21
|
+
|
22
|
+
Melomel needs to be embedded and running in the application you're trying to
|
23
|
+
connect to. Please see the Melomel repository for instructions on installation.
|
24
|
+
|
25
|
+
|
26
|
+
## HACKING
|
27
|
+
|
28
|
+
To get started with working with the source, start by running Bundler to get all
|
29
|
+
your dependencies:
|
30
|
+
|
31
|
+
[sudo] bundle install
|
32
|
+
|
33
|
+
If you are adding new features, please add test coverage to all code that you
|
34
|
+
write. First compile the Flex application and then run the test suite with
|
35
|
+
`rake`:
|
36
|
+
|
37
|
+
ant
|
38
|
+
rake test
|
39
|
+
|
40
|
+
|
41
|
+
## CONTRIBUTE
|
42
|
+
|
43
|
+
If you'd like to contribute to Melomel.rb, please fork the repo on GitHub:
|
44
|
+
|
45
|
+
http://github.com/benbjohnson/melomel.rb
|
46
|
+
|
47
|
+
To get all of the dependencies, install the gem first. The best way to get
|
48
|
+
your changes merged back into core is as follows:
|
49
|
+
|
50
|
+
1. Clone your fork locally
|
51
|
+
1. Create a named topic branch to contain your change
|
52
|
+
1. Code
|
53
|
+
1. All code must have RSpec test coverage. Run `rake` to ensure everything
|
54
|
+
passes.
|
55
|
+
1. If you are adding new functionality, document it in the README
|
56
|
+
1. If necessary, rebase your commits into logical chunks, without errors
|
57
|
+
1. Push the branch up to GitHub
|
58
|
+
1. Send me a pull request for your branch
|
data/lib/melomel.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.index(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'object'
|
4
|
+
require 'melomel/bridge'
|
5
|
+
require 'melomel/date'
|
6
|
+
require 'melomel/error'
|
7
|
+
require 'melomel/flex'
|
8
|
+
require 'melomel/object_proxy'
|
9
|
+
require 'melomel/version'
|
10
|
+
|
11
|
+
# This class acts as a singleton instance of the bridge. This is typically the
|
12
|
+
# only class you'll need to use. If multiple instances of the bridge are needed
|
13
|
+
# (to connect to multiple SWF files), you will need to instantiate and manage
|
14
|
+
# the bridge objects manually.
|
15
|
+
module Melomel
|
16
|
+
class MelomelError < StandardError; end
|
17
|
+
class UnrecognizedTypeError < MelomelError; end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
attr_reader :bridge
|
21
|
+
|
22
|
+
# Opens a bridge connection to the SWF file. This is a blocking call and
|
23
|
+
# will wait until a SWF connects before continuing.
|
24
|
+
def connect(host='localhost', port=10101)
|
25
|
+
@bridge = Melomel::Bridge.new(host, port)
|
26
|
+
@bridge.connect();
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(method, *args)
|
30
|
+
@bridge.__send__(method.to_sym, *args)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retrieves a reference to a class
|
34
|
+
def get_class(*args)
|
35
|
+
@bridge.get_class(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates an object in the virtual machine.
|
39
|
+
def create_object(class_name)
|
40
|
+
@bridge.create_object(class_name)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Invokes package level function
|
44
|
+
def invoke_function(function, *args)
|
45
|
+
@bridge.invoke_function(function, *args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'melomel/bridge/messaging'
|
3
|
+
require 'melomel/bridge/ui'
|
4
|
+
|
5
|
+
# The bridge manages the connection to the Flash virtual machine. All messages
|
6
|
+
# sent to the virtual machine are passed through this object.
|
7
|
+
module Melomel
|
8
|
+
class Bridge
|
9
|
+
attr_accessor :host, :port
|
10
|
+
|
11
|
+
def initialize(host='localhost', port=10101)
|
12
|
+
self.host = host
|
13
|
+
self.port = port
|
14
|
+
server, @socket = nil, nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Opens a socket connection to listen for incoming bridge connections. This
|
18
|
+
# method automatically handles requests for the policy file.
|
19
|
+
def connect()
|
20
|
+
disconnect()
|
21
|
+
|
22
|
+
# Listen for connections
|
23
|
+
server = TCPServer.open(host, port)
|
24
|
+
|
25
|
+
# Retrieve socket and check for initial handshake
|
26
|
+
while(@socket.nil?) do
|
27
|
+
socket = server.accept()
|
28
|
+
data = socket.gets("\x00").chomp("\x00")
|
29
|
+
|
30
|
+
# Send policy file if requested.
|
31
|
+
if(data == '<policy-file-request/>')
|
32
|
+
send_policy_file(socket)
|
33
|
+
# Otherwise open connection and continue
|
34
|
+
elsif(data == '<connect/>')
|
35
|
+
@socket = socket
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
server.close();
|
40
|
+
end
|
41
|
+
|
42
|
+
# Closes any open connection to a Flash virtual machine.
|
43
|
+
def disconnect()
|
44
|
+
begin
|
45
|
+
@socket.close()
|
46
|
+
rescue
|
47
|
+
end
|
48
|
+
|
49
|
+
@socket = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# Sends a message over to the Flash bridge.
|
53
|
+
def send(message)
|
54
|
+
@socket.puts("#{message}\x00")
|
55
|
+
end
|
56
|
+
|
57
|
+
# Receives a message from the Flash bridge. This is a blocking call.
|
58
|
+
def receive()
|
59
|
+
@socket.gets("\x00").chomp("\x00")
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
private
|
64
|
+
def send_policy_file(socket)
|
65
|
+
policy = ''
|
66
|
+
policy << '<?xml version="1.0"?>'
|
67
|
+
policy << '<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">'
|
68
|
+
policy << '<cross-domain-policy>'
|
69
|
+
policy << "<allow-access-from domain=\"#{host}\" to-ports=\"#{port}\"/>"
|
70
|
+
policy << '</cross-domain-policy>'
|
71
|
+
socket.send(policy, 0)
|
72
|
+
socket.flush()
|
73
|
+
socket.close()
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
# These add-on methods enable the message encoding and decoding.
|
4
|
+
module Melomel
|
5
|
+
class Bridge
|
6
|
+
###########################################################################
|
7
|
+
#
|
8
|
+
# Basic Messages
|
9
|
+
#
|
10
|
+
###########################################################################
|
11
|
+
|
12
|
+
# Creates an object in the Flash virtual machine and returns the reference
|
13
|
+
# to it.
|
14
|
+
#
|
15
|
+
# class_name - The name of the class to instantiate.
|
16
|
+
#
|
17
|
+
# Returns an instance of the class if the class is found. Otherwise, nil.
|
18
|
+
def create_object(class_name)
|
19
|
+
send_create_object(class_name, false)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Same as `create_object` except that an error is thrown if the class is
|
23
|
+
# not found.
|
24
|
+
def create_object!(class_name)
|
25
|
+
send_create_object(class_name, true)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Retrieves a reference to a class in the Flash virtual machine.
|
29
|
+
#
|
30
|
+
# class_name - The name of the class to retrieve a reference to.
|
31
|
+
#
|
32
|
+
# Returns a reference to the class if it exists. Otherwise, nil.
|
33
|
+
def get_class(class_name)
|
34
|
+
send_get_class(class_name, false)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Same as `get_class` except that an error is thrown if the class is not
|
38
|
+
# found.
|
39
|
+
def get_class!(class_name)
|
40
|
+
send_get_class(class_name, true)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Retrieves a property of an object in the Flash virtual machine
|
44
|
+
#
|
45
|
+
# proxy_id - The identifier for the object proxy.
|
46
|
+
# property - The name of the property to access.
|
47
|
+
#
|
48
|
+
# Returns the value of the property if it exists. Otherwise, nil.
|
49
|
+
def get_property(proxy_id, property)
|
50
|
+
send_get_property(proxy_id, property, false)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Same as `get_property` except that an error is thrown if the property
|
54
|
+
# does not exist.
|
55
|
+
def get_property!(proxy_id, property)
|
56
|
+
send_get_property(proxy_id, property, true)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Sets a property on an object in the Flash virtual machine
|
60
|
+
#
|
61
|
+
# proxy_id - The identifier of the object proxy.
|
62
|
+
# property - The name of the property to mutate.
|
63
|
+
# value - The value to set the property to.
|
64
|
+
#
|
65
|
+
# Returns the value of the property after being set.
|
66
|
+
def set_property(proxy_id, property, value)
|
67
|
+
send_set_property(proxy_id, property, value, false)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Same as `set_property` except that an error is thrown if the property
|
71
|
+
# does not exist.
|
72
|
+
def set_property!(proxy_id, property, value)
|
73
|
+
send_set_property(proxy_id, property, value, true)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Invokes a method on an object in the Flash virtual machine
|
77
|
+
#
|
78
|
+
# proxy_id - The identifier of the object proxy.
|
79
|
+
# method_name - The name of the method to invoke.
|
80
|
+
# *args - List of arguments passed to the method.
|
81
|
+
#
|
82
|
+
# Returns the value returned from the Flash method.
|
83
|
+
def invoke_method(proxy_id, method_name, *args)
|
84
|
+
send_invoke_method(proxy_id, method_name, args, false)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Same as `invoke_method` except that an error is thrown if the method
|
88
|
+
# does not exist.
|
89
|
+
def invoke_method!(proxy_id, method_name, *args)
|
90
|
+
send_invoke_method(proxy_id, method_name, args, true)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Invokes a package level function in the Flash virtual machine
|
94
|
+
#
|
95
|
+
# function_name - The fully qualified name of the function to invoke.
|
96
|
+
# *args - List of arguments passed to the function.
|
97
|
+
#
|
98
|
+
# Returns the return value of the Flash function.
|
99
|
+
def invoke_function(function_name, *args)
|
100
|
+
send_invoke_function(function_name, args, false)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Same as `invoke_function` except that an error is thrown if the method
|
104
|
+
# does not exist.
|
105
|
+
def invoke_function!(function_name, *args)
|
106
|
+
send_invoke_function(function_name, args, true)
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
###########################################################################
|
111
|
+
#
|
112
|
+
# Utility Methods
|
113
|
+
#
|
114
|
+
###########################################################################
|
115
|
+
|
116
|
+
# Formats a Ruby value into an XML message
|
117
|
+
def format_message_value(xml, value)
|
118
|
+
# Automatically convert simple objects to proxies.
|
119
|
+
value = value.to_object_proxy(self) unless value.nil?
|
120
|
+
|
121
|
+
if value.nil?
|
122
|
+
xml['dataType'] = 'null'
|
123
|
+
elsif value.class == Fixnum || value.class == Bignum
|
124
|
+
xml['value'] = value.to_s
|
125
|
+
xml['dataType'] = 'int'
|
126
|
+
elsif value.class == Float
|
127
|
+
xml['value'] = value.to_s
|
128
|
+
xml['dataType'] = 'float'
|
129
|
+
elsif value == true || value == false
|
130
|
+
xml['value'] = value.to_s
|
131
|
+
xml['dataType'] = 'boolean'
|
132
|
+
elsif value.class == Melomel::ObjectProxy
|
133
|
+
xml['value'] = value.proxy_id.to_s
|
134
|
+
xml['dataType'] = 'object'
|
135
|
+
elsif value.class == String
|
136
|
+
xml['value'] = value
|
137
|
+
xml['dataType'] = 'string'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Parses a return message and converts it into an appropriate type
|
142
|
+
def parse_message_value(xml)
|
143
|
+
name = xml.name()
|
144
|
+
|
145
|
+
# If we receive an error back then raise it in the Ruby VM.
|
146
|
+
if name == 'error'
|
147
|
+
stack_trace_xml = xml.at_xpath('stack-trace')
|
148
|
+
object = Melomel::ObjectProxy.new(self, xml['proxyId'].to_i)
|
149
|
+
error_id = xml['errorId'].to_i
|
150
|
+
message = xml['message']
|
151
|
+
name = xml['name']
|
152
|
+
stack_trace = stack_trace_xml ? stack_trace_xml.to_str : nil
|
153
|
+
$stderr.puts(stack_trace)
|
154
|
+
raise Melomel::Error.new(object, error_id, message, name, stack_trace), message
|
155
|
+
|
156
|
+
# Otherwise we have a return value so we should parse it.
|
157
|
+
else
|
158
|
+
value = xml['value']
|
159
|
+
data_type = xml['dataType']
|
160
|
+
|
161
|
+
if data_type == 'null'
|
162
|
+
return nil
|
163
|
+
elsif data_type == 'int'
|
164
|
+
return value.to_i
|
165
|
+
elsif data_type == 'float'
|
166
|
+
return value.to_f
|
167
|
+
elsif data_type == 'boolean'
|
168
|
+
return value == 'true'
|
169
|
+
elsif data_type == 'object'
|
170
|
+
return Melomel::ObjectProxy.new(self, value.to_i)
|
171
|
+
elsif data_type == 'string' || data_type.nil?
|
172
|
+
return value
|
173
|
+
else
|
174
|
+
raise UnrecognizedTypeError, "Unknown type: #{data_type}"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
###########################################################################
|
181
|
+
#
|
182
|
+
# Private Methods
|
183
|
+
#
|
184
|
+
###########################################################################
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
# Creates an object in the Flash virtual machine and returns the reference
|
189
|
+
# to it.
|
190
|
+
def send_create_object(class_name, throwable=true)
|
191
|
+
send("<create class=\"#{class_name}\" throwable=\"#{throwable}\"/>")
|
192
|
+
parse_message_value(Nokogiri::XML(receive()).root)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Retrieves a reference to a class in the Flash virtual machine.
|
196
|
+
def send_get_class(class_name, throwable=true)
|
197
|
+
send("<get-class name=\"#{class_name}\" throwable=\"#{throwable}\"/>")
|
198
|
+
parse_message_value(Nokogiri::XML(receive()).root)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Retrieves a property of an object in the Flash virtual machine
|
202
|
+
def send_get_property(proxy_id, property, throwable)
|
203
|
+
send("<get object=\"#{proxy_id}\" property=\"#{property}\" throwable=\"#{throwable}\"/>")
|
204
|
+
parse_message_value(Nokogiri::XML(receive()).root)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Sets a property on an object in the Flash virtual machine
|
208
|
+
def send_set_property(proxy_id, property, value, throwable)
|
209
|
+
# Create message and format value to set
|
210
|
+
xml = Nokogiri::XML("<set object=\"#{proxy_id}\" property=\"#{property}\" throwable=\"#{throwable}\"><arg/></set>")
|
211
|
+
format_message_value(xml.at_xpath('/set/arg'), value)
|
212
|
+
|
213
|
+
# Send & Receive
|
214
|
+
send(xml.root.to_xml(:indent => 0))
|
215
|
+
parse_message_value(Nokogiri::XML(receive()).root)
|
216
|
+
end
|
217
|
+
|
218
|
+
# Invokes a method on an object in the Flash virtual machine
|
219
|
+
def send_invoke_method(proxy_id, method_name, args, throwable)
|
220
|
+
xml = Nokogiri::XML("<invoke object=\"#{proxy_id}\" method=\"#{method_name}\" throwable=\"#{throwable}\"><args/></invoke>")
|
221
|
+
|
222
|
+
# Loop over and add arguments to call
|
223
|
+
args_node = xml.at_xpath('invoke/args')
|
224
|
+
args.each do |arg|
|
225
|
+
arg_node = Nokogiri::XML::Node.new('arg', xml)
|
226
|
+
format_message_value(arg_node, arg)
|
227
|
+
args_node.add_child(arg_node)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Send and receive
|
231
|
+
send(xml.root.to_xml(:indent => 0))
|
232
|
+
parse_message_value(Nokogiri::XML(receive()).root)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Invokes a package level function in the Flash virtual machine
|
236
|
+
def send_invoke_function(function_name, args, throwable)
|
237
|
+
xml = Nokogiri::XML("<invoke-function name=\"#{function_name}\" throwable=\"#{throwable}\"><args/></invoke>")
|
238
|
+
|
239
|
+
# Loop over and add arguments to call
|
240
|
+
args_node = xml.at_xpath('invoke-function/args')
|
241
|
+
args.each do |arg|
|
242
|
+
arg_node = Nokogiri::XML::Node.new('arg', xml)
|
243
|
+
format_message_value(arg_node, arg)
|
244
|
+
args_node.add_child(arg_node)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Send and receive
|
248
|
+
send(xml.root.to_xml(:indent => 0))
|
249
|
+
parse_message_value(Nokogiri::XML(receive()).root)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|