kolach-melomel 0.6.4
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/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
|