watirgrid 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/NOTES.txt +14 -0
- data/README.rdoc +56 -0
- data/Rakefile +44 -0
- data/TODO.txt +16 -0
- data/bin/controller +49 -0
- data/bin/provider +48 -0
- data/lib/controller.rb +101 -0
- data/lib/provider.rb +156 -0
- data/lib/watirgrid.rb +123 -0
- data/spec/grid_spec.rb +121 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/stub.rb +7 -0
- data/spec/watirgrid_spec.rb +42 -0
- data/watirgrid.gemspec +59 -0
- metadata +86 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Tim Koopmans
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/NOTES.txt
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
= Release Notes
|
2
|
+
== 0.0.1
|
3
|
+
Added access control lists for the controller and provider. This lets the user specify ACLs by way of simple white/blacklist security similar to that used by the Unix /etc/hosts.allow and /etc/hosts.deny files. The ACL constructor takes an array of strings. The first string of a pair is always “allow” or “deny”, and it’s followed by the address or addresses to allow or deny access.
|
4
|
+
|
5
|
+
e.g. ./provider.rb -a deny,all,allow,127.0.0.1
|
6
|
+
This would deny all access to this provider by default and allow access only from the localhost (127.0.0.1). This is the default setting if no ACLs are specified from the command line. This would affect all connectivity from the controller to the providers.
|
7
|
+
|
8
|
+
e.g. ./controller.rb -a deny,all,allow,192.168.1.*
|
9
|
+
This would deny all access to the controller by default with access allowed from any host in the 192.168.1.* subnet. This would affect all connectivity from the providers to the controller.
|
10
|
+
|
11
|
+
---
|
12
|
+
|
13
|
+
Added startup bin scripts for the provider and controller. Ruby Gems will automatically deploy these on installation.
|
14
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= watirgrid
|
2
|
+
|
3
|
+
WatirGrid allows for distributed testing across a grid network using Watir.
|
4
|
+
|
5
|
+
Basic instructions are:
|
6
|
+
|
7
|
+
Pick a server to host the 'controller' for the ring server and execute the following:
|
8
|
+
$ controller -l INFO
|
9
|
+
|
10
|
+
This should find your external facing IP and start the corresponding DRb and Ring Servers:
|
11
|
+
DRb server started on : druby://192.168.1.105:11235
|
12
|
+
Ring server started on: druby://192.168.1.105:12358
|
13
|
+
|
14
|
+
On each client PC, host the 'provider' for the distributed (DRb) Watir objects
|
15
|
+
$ provider -l INFO
|
16
|
+
|
17
|
+
This should find the recently started Ring Server and register a tuple for the Watir object:
|
18
|
+
DRb server started on : druby://192.168.1.105:64864
|
19
|
+
New tuple registered : druby://192.168.1.105:12358
|
20
|
+
|
21
|
+
You will now be able to execute commands across remote browsers on your grid network.
|
22
|
+
|
23
|
+
Check the watirgid_spec.rb for some examples ...
|
24
|
+
|
25
|
+
e.g.
|
26
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
27
|
+
browsers.start(:quantity => 2, :read_all => true)
|
28
|
+
browsers.size.should == 2
|
29
|
+
browsers.each do |browser, browser_id|
|
30
|
+
browser.goto("http://www.google.com")
|
31
|
+
browser.text_field(:name, 'q').set("watirgrid")
|
32
|
+
browser.button(:name, "btnI").click
|
33
|
+
end
|
34
|
+
|
35
|
+
For more help see:
|
36
|
+
$ controller -h
|
37
|
+
Usage: controller [options]
|
38
|
+
Specific options:
|
39
|
+
-d, --drb-server-port PORT Specify DRb Server port to listen on
|
40
|
+
-r, --ring-server-port PORT Specify Ring Server port to listen on
|
41
|
+
-l, --log-level LEVEL Specify log level {DEBUG|INFO|ERROR}
|
42
|
+
-h, --help Show this message
|
43
|
+
|
44
|
+
$ ./provider -h
|
45
|
+
Usage: provider [options]
|
46
|
+
Specific options:
|
47
|
+
-r, --ring-server-port PORT Specify Ring Server port to broadcast on
|
48
|
+
-b, --browser-type TYPE Specify browser type to register {ie|firefox|safari}
|
49
|
+
-l, --log-level LEVEL Specify log level {DEBUG|INFO|ERROR}
|
50
|
+
-h, --help Show this message
|
51
|
+
|
52
|
+
I'll update the wiki soon'ish!
|
53
|
+
|
54
|
+
== Copyright
|
55
|
+
|
56
|
+
Copyright (c) 2009 Tim Koopmans. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "watirgrid"
|
8
|
+
gem.summary = %Q{WatirGrid: Web Application Testing in Ruby across a grid network.}
|
9
|
+
gem.description = %Q{WatirGrid allows for distributed testing across a grid network using Watir.}
|
10
|
+
gem.email = "tim.koops@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/90kts/watirgrid"
|
12
|
+
gem.authors = ["Tim Koopmans"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.version = "0.0.1"
|
15
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
|
+
end
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "watirgrid #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
data/TODO.txt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
== Docs
|
2
|
+
better instructions in general
|
3
|
+
|
4
|
+
== Providers
|
5
|
+
Remote registration of providers
|
6
|
+
Re-write tuple space during tear down
|
7
|
+
Response times
|
8
|
+
Network breakdown times
|
9
|
+
|
10
|
+
== Protocol
|
11
|
+
Support for tcp or http?
|
12
|
+
|
13
|
+
== Tests
|
14
|
+
How many providers is suitable with druby
|
15
|
+
Comparison in performance with proxied objects (DRbUndumped) vs. copied objects
|
16
|
+
|
data/bin/controller
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'controller'
|
4
|
+
require 'provider'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: controller [options]"
|
9
|
+
opts.separator ""
|
10
|
+
opts.separator "Specific options:"
|
11
|
+
opts.on("-d PORT", "--drb-server-port", Integer,
|
12
|
+
"Specify DRb Server port to listen on") do |d|
|
13
|
+
options[:drb_server_port] = d
|
14
|
+
end
|
15
|
+
opts.on("-r PORT", "--ring-server-port", Integer,
|
16
|
+
"Specify Ring Server port to listen on") do |r|
|
17
|
+
options[:ring_server_port] = r
|
18
|
+
end
|
19
|
+
opts.on("-a ACLS", "--access-control-list", Array,
|
20
|
+
"Specify a comma separated Access Control List") do |a|
|
21
|
+
options[:acls] = a
|
22
|
+
end
|
23
|
+
opts.on("-l LEVEL", "--log-level", String,
|
24
|
+
"Specify log level {DEBUG|INFO|ERROR}") do |l|
|
25
|
+
case l
|
26
|
+
when 'DEBUG'
|
27
|
+
options[:loglevel] = Logger::DEBUG
|
28
|
+
when 'INFO'
|
29
|
+
options[:loglevel] = Logger::INFO
|
30
|
+
when 'ERROR'
|
31
|
+
options[:loglevel] = Logger::ERROR
|
32
|
+
else
|
33
|
+
options[:loglevel] = Logger::ERROR
|
34
|
+
end
|
35
|
+
end
|
36
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
end.parse!
|
41
|
+
|
42
|
+
controller = Controller.new(
|
43
|
+
:drb_server_port => options[:drb_server_port] || 11235,
|
44
|
+
:ring_server_port => options[:ring_server_port] || 12358,
|
45
|
+
:acls => options[:acls] || %w{ deny all allow 127.0.0.1 },
|
46
|
+
:loglevel => options[:loglevel]
|
47
|
+
)
|
48
|
+
controller.start
|
49
|
+
DRb.thread.join
|
data/bin/provider
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rubygems'
|
3
|
+
require 'controller'
|
4
|
+
require 'provider'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: provider [options]"
|
9
|
+
opts.separator ""
|
10
|
+
opts.separator "Specific options:"
|
11
|
+
opts.on("-r PORT", "--ring-server-port", Integer,
|
12
|
+
"Specify Ring Server port to broadcast on") do |r|
|
13
|
+
options[:ring_server_port] = r
|
14
|
+
end
|
15
|
+
opts.on("-b TYPE", "--browser-type", String,
|
16
|
+
"Specify browser type to register {ie|firefox|safari}") do |b|
|
17
|
+
options[:browser_type] = b
|
18
|
+
end
|
19
|
+
opts.on("-a ACLS", "--access-control-list", Array,
|
20
|
+
"Specify a comma separated Access Control List") do |a|
|
21
|
+
options[:acls] = a
|
22
|
+
end
|
23
|
+
opts.on("-l LEVEL", "--log-level", String,
|
24
|
+
"Specify log level {DEBUG|INFO|ERROR}") do |l|
|
25
|
+
case l
|
26
|
+
when 'DEBUG'
|
27
|
+
options[:loglevel] = Logger::DEBUG
|
28
|
+
when 'INFO'
|
29
|
+
options[:loglevel] = Logger::INFO
|
30
|
+
when 'ERROR'
|
31
|
+
options[:loglevel] = Logger::ERROR
|
32
|
+
else
|
33
|
+
options[:loglevel] = Logger::ERROR
|
34
|
+
end
|
35
|
+
end
|
36
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
end.parse!
|
41
|
+
|
42
|
+
provider = Provider.new(
|
43
|
+
:ring_server_port => options[:ring_server_port] || 12358,
|
44
|
+
:browser_type => options[:browser_type] || nil,
|
45
|
+
:acls => options[:acls] || %w{ deny all allow 127.0.0.1 },
|
46
|
+
:loglevel => options[:loglevel])
|
47
|
+
provider.start
|
48
|
+
DRb.thread.join
|
data/lib/controller.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# controller.rb
|
3
|
+
# Rinda Ring Server Controlller
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rinda/tuplespace'
|
7
|
+
require 'rinda/ring'
|
8
|
+
require 'logger'
|
9
|
+
require 'optparse'
|
10
|
+
require 'drb/acl'
|
11
|
+
|
12
|
+
module Rinda
|
13
|
+
|
14
|
+
##
|
15
|
+
# Extend Rinda::RingServer to allow a hostname/ipaddress
|
16
|
+
# to be passed in as parameter arguments. Also pass back
|
17
|
+
# attribute for ring server uri.
|
18
|
+
#
|
19
|
+
class RingServer
|
20
|
+
attr_accessor :uri
|
21
|
+
|
22
|
+
def initialize(ts, host='', port=Ring_PORT)
|
23
|
+
@uri = "druby://#{host}:#{port}"
|
24
|
+
@ts = ts
|
25
|
+
@soc = UDPSocket.open
|
26
|
+
@soc.bind(host, port)
|
27
|
+
@w_service = write_service
|
28
|
+
@r_service = reply_service
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Controller
|
34
|
+
|
35
|
+
attr_accessor :drb_server_uri, :ring_server_uri
|
36
|
+
|
37
|
+
def initialize(params = {})
|
38
|
+
@host = params[:interface] || external_interface
|
39
|
+
@drb_server_port = params[:drb_server_port] || 0
|
40
|
+
@ring_server_port = params[:ring_server_port] || Rinda::Ring_PORT
|
41
|
+
@acls = params[:acls]
|
42
|
+
|
43
|
+
logfile = params[:logfile] || STDOUT
|
44
|
+
@log = Logger.new(logfile, 'daily')
|
45
|
+
@log.level = params[:loglevel] || Logger::INFO
|
46
|
+
@log.datetime_format = "%Y-%m-%d %H:%M:%S "
|
47
|
+
|
48
|
+
@log.debug("DRB Server Port #{@drb_server_port}\nRing Server Port #{@ring_server_port}")
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Start a new tuplespace on the ring server
|
53
|
+
def start
|
54
|
+
# create a parent Tuple Space
|
55
|
+
tuple_space = Rinda::TupleSpace.new
|
56
|
+
|
57
|
+
# Setup the security--remember to call before DRb.start_service()
|
58
|
+
DRb.install_acl(ACL.new(@acls))
|
59
|
+
|
60
|
+
# start the DRb Server
|
61
|
+
drb_server = DRb.start_service("druby://#{@host}:#{@drb_server_port}", tuple_space)
|
62
|
+
|
63
|
+
# obtain DRb Server uri
|
64
|
+
@drb_server_uri = drb_server.uri
|
65
|
+
@log.info("DRb server started on : #{@drb_server_uri}")
|
66
|
+
|
67
|
+
# start the Ring Server
|
68
|
+
ring_server = Rinda::RingServer.new(tuple_space, @host, @ring_server_port)
|
69
|
+
|
70
|
+
# obtain Ring Server uri
|
71
|
+
@ring_server_uri = ring_server.uri
|
72
|
+
@log.info("Ring server started on: #{@ring_server_uri}")
|
73
|
+
|
74
|
+
# abort all threads on an exception
|
75
|
+
Thread.abort_on_exception = true
|
76
|
+
|
77
|
+
# wait for explicit stop via ctrl-c
|
78
|
+
DRb.thread.join if __FILE__ == $0
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Stop the controller by shutting down the DRb service
|
83
|
+
def stop
|
84
|
+
DRb.stop_service
|
85
|
+
@log.info("DRb server stopped on: #{@drb_server_uri}")
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
##
|
91
|
+
# Get the external facing interface for this server
|
92
|
+
def external_interface
|
93
|
+
begin
|
94
|
+
UDPSocket.open {|s| s.connect('watir.com', 1); s.addr.last }
|
95
|
+
rescue
|
96
|
+
'127.0.0.1'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
data/lib/provider.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# provider.rb
|
3
|
+
# Rinda Ring Provider
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'rinda/ring'
|
7
|
+
require 'rinda/tuplespace'
|
8
|
+
require 'logger'
|
9
|
+
require 'optparse'
|
10
|
+
require 'drb/acl'
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'watir'
|
14
|
+
rescue LoadError
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'safariwatir'
|
19
|
+
rescue LoadError
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'firewatir'
|
24
|
+
include FireWatir
|
25
|
+
rescue LoadError
|
26
|
+
end
|
27
|
+
|
28
|
+
module Watir
|
29
|
+
|
30
|
+
##
|
31
|
+
# Extend Watir with a Provider class
|
32
|
+
# to determine which browser type is supported by the
|
33
|
+
# remote DRb process. This returns the DRb front object.
|
34
|
+
class Provider
|
35
|
+
|
36
|
+
include DRbUndumped # all objects will be proxied, not copied
|
37
|
+
|
38
|
+
attr_reader :browser
|
39
|
+
|
40
|
+
def initialize(browser = nil)
|
41
|
+
browser = (browser || 'tmp').downcase.to_sym
|
42
|
+
case browser
|
43
|
+
when :safari
|
44
|
+
@browser = Watir::Safari
|
45
|
+
when :firefox
|
46
|
+
@browser = FireWatir::Firefox
|
47
|
+
when :ie
|
48
|
+
@browser = Watir::IE
|
49
|
+
else
|
50
|
+
@browser = find_supported_browser
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_supported_browser
|
55
|
+
if Watir::Safari then return Watir::Safari end
|
56
|
+
if Watir::IE then return Watir::IE end
|
57
|
+
if FireWatir::Firefox then return FireWatir::Firefox end
|
58
|
+
end
|
59
|
+
|
60
|
+
def new_browser
|
61
|
+
if @browser.nil?
|
62
|
+
find_supported_browser.new
|
63
|
+
else
|
64
|
+
@browser.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
class Provider
|
73
|
+
|
74
|
+
attr_accessor :drb_server_uri, :ring_server_uri
|
75
|
+
|
76
|
+
def initialize(params = {})
|
77
|
+
@host = params[:interface] || external_interface
|
78
|
+
@drb_server_port = params[:drb_server_port] || 0
|
79
|
+
@ring_server_port = params[:ring_server_port] || Rinda::Ring_PORT
|
80
|
+
|
81
|
+
@renewer = params[:renewer] || Rinda::SimpleRenewer.new
|
82
|
+
@browser_type = params[:browser_type] || nil
|
83
|
+
|
84
|
+
logfile = params[:logfile] || STDOUT
|
85
|
+
@log = Logger.new(logfile, 'daily')
|
86
|
+
@log.level = params[:loglevel] || Logger::INFO
|
87
|
+
@log.datetime_format = "%Y-%m-%d %H:%M:%S "
|
88
|
+
|
89
|
+
@log.debug("DRB Server Port #{@drb_server_port}\nRing Server Port #{@ring_server_port}")
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Start providing watir objects on the ring server
|
94
|
+
def start
|
95
|
+
# create a DRb 'front' object
|
96
|
+
watir_provider = Watir::Provider.new(@browser_type)
|
97
|
+
architecture = Config::CONFIG['arch']
|
98
|
+
hostname = ENV['SERVER_NAME'] || %x{hostname}.strip
|
99
|
+
|
100
|
+
# Setup the security--remember to call before DRb.start_service()
|
101
|
+
DRb.install_acl(ACL.new(@acls))
|
102
|
+
|
103
|
+
# start the DRb Server
|
104
|
+
drb_server = DRb.start_service("druby://#{@host}:#{@drb_server_port}")
|
105
|
+
|
106
|
+
# obtain DRb Server uri
|
107
|
+
@drb_server_uri = drb_server.uri
|
108
|
+
@log.info("DRb server started on : #{@drb_server_uri}")
|
109
|
+
|
110
|
+
# create a service tuple
|
111
|
+
@tuple = [
|
112
|
+
:name,
|
113
|
+
:WatirProvider,
|
114
|
+
watir_provider,
|
115
|
+
'A watir provider',
|
116
|
+
hostname,
|
117
|
+
architecture,
|
118
|
+
@browser_type
|
119
|
+
]
|
120
|
+
|
121
|
+
# locate the Rinda Ring Server via a UDP broadcast
|
122
|
+
ring_server = Rinda::RingFinger.new(@host, @ring_server_port)
|
123
|
+
ring_server = ring_server.lookup_ring_any
|
124
|
+
@log.info("Ring server found on : druby://#{@host}:#{@ring_server_port}")
|
125
|
+
|
126
|
+
# advertise this service on the primary remote tuple space
|
127
|
+
ring_server.write(@tuple, @renewer)
|
128
|
+
|
129
|
+
# log DRb server uri
|
130
|
+
@log.info("New tuple registered : druby://#{@host}:#{@ring_server_port}")
|
131
|
+
|
132
|
+
# wait for explicit stop via ctrl-c
|
133
|
+
DRb.thread.join if __FILE__ == $0
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Stop the provider by shutting down the DRb service
|
138
|
+
def stop
|
139
|
+
DRb.stop_service
|
140
|
+
@log.info("DRb server stopped on : #{@drb_server_uri}")
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
##
|
146
|
+
# Get the external facing interface for this server
|
147
|
+
def external_interface
|
148
|
+
begin
|
149
|
+
UDPSocket.open {|s| s.connect('watir.com', 1); s.addr.last }
|
150
|
+
rescue
|
151
|
+
'127.0.0.1'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
data/lib/watirgrid.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'controller'
|
3
|
+
require 'provider'
|
4
|
+
|
5
|
+
module Watir
|
6
|
+
|
7
|
+
##
|
8
|
+
# Extend Watir with a Grid class which
|
9
|
+
# implements a grid of browsers by connecting to a tuplespace
|
10
|
+
# and instatiating remote browser objects on nominated providers.
|
11
|
+
class Grid
|
12
|
+
|
13
|
+
attr_accessor :drb_server_uri, :ring_server, :browsers
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
@host = params[:interface] || external_interface
|
17
|
+
@drb_server_port = params[:drb_server_port] || 0
|
18
|
+
@ring_server_port = params[:ring_server_port] || Rinda::Ring_PORT
|
19
|
+
@renewer = params[:renewer] || Rinda::SimpleRenewer.new
|
20
|
+
|
21
|
+
@quantity = params[:quantity]
|
22
|
+
|
23
|
+
logfile = params[:logfile] || STDOUT
|
24
|
+
@log = Logger.new(logfile, 'daily')
|
25
|
+
@log.level = params[:loglevel] || Logger::ERROR
|
26
|
+
@log.datetime_format = "%Y-%m-%d %H:%M:%S "
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Start required services
|
31
|
+
def start(params = {})
|
32
|
+
start_drb_server
|
33
|
+
find_ring_server
|
34
|
+
get_tuples(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Yield a browser object when iterating over the grid of browsers
|
39
|
+
def each
|
40
|
+
threads = []
|
41
|
+
id = 0
|
42
|
+
@browsers.each do |browser|
|
43
|
+
threads << Thread.new do
|
44
|
+
id += 1
|
45
|
+
yield(browser, id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
threads.each {|thread| thread.join}
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Return the size (quantity) of browsers started on the grid
|
53
|
+
def size
|
54
|
+
@browsers.size
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
##
|
60
|
+
# Get the external facing interface for this server
|
61
|
+
def external_interface
|
62
|
+
begin
|
63
|
+
UDPSocket.open {|s| s.connect('watir.com', 1); s.addr.last }
|
64
|
+
rescue
|
65
|
+
'127.0.0.1'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Start the DRb Server
|
71
|
+
def start_drb_server
|
72
|
+
drb_server = DRb.start_service("druby://#{@host}:#{@drb_server_port}")
|
73
|
+
@drb_server_uri = drb_server.uri
|
74
|
+
@log.info("DRb server started on : #{@drb_server_uri}")
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# Locate the Rinda Ring Server via a UDP broadcast
|
79
|
+
def find_ring_server
|
80
|
+
@ring_server = Rinda::RingFinger.new(@host, @ring_server_port)
|
81
|
+
@ring_server = @ring_server.lookup_ring_any
|
82
|
+
@log.info("Ring server found on : druby://#{@host}:#{@ring_server_port}")
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Get all tuple spaces on ringserver
|
87
|
+
def get_tuples(params = {})
|
88
|
+
quantity = params[:quantity] || -1
|
89
|
+
architecture = params[:architecture] || nil
|
90
|
+
browser_type = params[:browser_type] || nil
|
91
|
+
|
92
|
+
@browsers = []
|
93
|
+
services = @ring_server.read_all([
|
94
|
+
:name,
|
95
|
+
nil, # watir provider
|
96
|
+
nil, # browser front object
|
97
|
+
nil, # provider description
|
98
|
+
nil, # hostname
|
99
|
+
architecture,
|
100
|
+
browser_type])
|
101
|
+
|
102
|
+
@log.info("Found #{services.size} services.")
|
103
|
+
if services.size > 0 then
|
104
|
+
services[1..quantity].each do |service|
|
105
|
+
hostname = service[4]
|
106
|
+
if params[:hostnames] then
|
107
|
+
if params[:hostnames][hostname] then
|
108
|
+
@browsers << service[2].new_browser
|
109
|
+
@ring_server.take(service)if params[:take_all] == true
|
110
|
+
end
|
111
|
+
else
|
112
|
+
@browsers << service[2].new_browser
|
113
|
+
@ring_server.take(service)if params[:take_all] == true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
else
|
117
|
+
@browsers
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
data/spec/grid_spec.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'WatirGrid' do
|
4
|
+
before(:all) do
|
5
|
+
controller = Controller.new(:ring_server_port => 12351,
|
6
|
+
:loglevel => Logger::ERROR)
|
7
|
+
controller.start
|
8
|
+
1.upto(5) do
|
9
|
+
provider = Provider.new(:ring_server_port => 12351,
|
10
|
+
:loglevel => Logger::ERROR, :browser_type => 'safari')
|
11
|
+
provider.start
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return how many browsers are available in the tuplespace' do
|
16
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
17
|
+
browsers.start(:read_all => true)
|
18
|
+
browsers.size.should == 4
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should read any 2 browsers in the tuplespace' do
|
22
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
23
|
+
browsers.start(:quantity => 2, :read_all => true)
|
24
|
+
browsers.size.should == 2
|
25
|
+
browsers.each do |browser, browser_id|
|
26
|
+
browser.goto(
|
27
|
+
"http://localhost:4567/load/#{browser_id}/#{browser.object_id}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should take any 1 browser in the tuplespace' do
|
32
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
33
|
+
browsers.start(:quantity => 1, :take_all => true)
|
34
|
+
browsers.size.should == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should take all browsers remaining in tuplespace' do
|
38
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
39
|
+
browsers.start(:take_all => true)
|
40
|
+
browsers.size.should == 3
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should find no more browsers in the tuplespace' do
|
44
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
45
|
+
browsers.start(:read_all => true)
|
46
|
+
browsers.size.should == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should register 3 new browsers in the tuplespace' do
|
50
|
+
1.upto(3) do
|
51
|
+
provider = Provider.new(:ring_server_port => 12351,
|
52
|
+
:loglevel => Logger::ERROR, :browser_type => 'safari')
|
53
|
+
provider.start
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should take any 1 browser based on browser type' do
|
58
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
59
|
+
browsers.start(:quantity => 1,
|
60
|
+
:take_all => true, :browser_type => 'safari')
|
61
|
+
browsers.size.should == 1
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should fail to find any browsers based on a specific browser type' do
|
65
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
66
|
+
browsers.start(:quantity => 1,
|
67
|
+
:take_all => true, :browser_type => 'firefox')
|
68
|
+
browsers.size.should == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should fail to find any browsers based on a unknown browser type' do
|
72
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
73
|
+
browsers.start(:quantity => 1,
|
74
|
+
:take_all => true, :browser_type => 'penguin')
|
75
|
+
browsers.size.should == 0
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should take any 1 browser based on specific architecture type' do
|
79
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
80
|
+
browsers.start(:quantity => 1,
|
81
|
+
:take_all => true, :architecture => 'universal-darwin10.0')
|
82
|
+
browsers.size.should == 1
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should fail to find any browsers based on unknown architecture type' do
|
86
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
87
|
+
browsers.start(:quantity => 1,
|
88
|
+
:take_all => true, :architecture => 'geos-1992')
|
89
|
+
browsers.size.should == 0
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should take any 1 browser based on specific hostname' do
|
93
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
94
|
+
browsers.start(:quantity => 1,
|
95
|
+
:take_all => true, :hostnames => {
|
96
|
+
"90kts.local" => "127.0.0.1"})
|
97
|
+
browsers.size.should == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should fail to find any browsers based on unknown hostname' do
|
101
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
102
|
+
browsers.start(:quantity => 1,
|
103
|
+
:take_all => true, :hostnames => {
|
104
|
+
"tokyo" => "127.0.0.1"})
|
105
|
+
browsers.size.should == 0
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should find no more browsers in the tuplespace' do
|
109
|
+
browsers = Watir::Grid.new(:ring_server_port => 12351)
|
110
|
+
browsers.start(:read_all => true)
|
111
|
+
browsers.size.should == 0
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should register a new browser on a remote provider' do
|
115
|
+
pending('provision of remote registration') do
|
116
|
+
browsers.size.should == 0
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
data/spec/stub.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Controller do
|
4
|
+
it 'should start a DRb and Ring Server when specifying NO interface or port' do
|
5
|
+
controller = Controller.new
|
6
|
+
controller.start
|
7
|
+
controller.drb_server_uri.should =~ /druby/
|
8
|
+
controller.stop
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should start a DRb and Ring Server on a specified interface' do
|
12
|
+
controller = Controller.new(:interface => '127.0.0.1')
|
13
|
+
controller.start
|
14
|
+
controller.drb_server_uri.should =~ /druby/
|
15
|
+
controller.stop
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should start a DRb and Ring Server on specified ports' do
|
19
|
+
controller = Controller.new(:drb_server_port => 11235,
|
20
|
+
:ring_server_port => 12358)
|
21
|
+
controller.start
|
22
|
+
controller.drb_server_uri.should =~ /druby/
|
23
|
+
controller.stop
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe Provider do
|
28
|
+
before(:all) do
|
29
|
+
@controller = Controller.new(:ring_server_port => 12350)
|
30
|
+
@controller.start
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should register a new provider on a specified port' do
|
34
|
+
provider = Provider.new(:ring_server_port => 12350)
|
35
|
+
provider.start
|
36
|
+
end
|
37
|
+
|
38
|
+
after(:all) do
|
39
|
+
@controller.stop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
data/watirgrid.gemspec
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{watirgrid}
|
8
|
+
s.version = ""
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Tim Koopmans"]
|
12
|
+
s.date = %q{2009-11-05}
|
13
|
+
s.description = %q{WatirGrid allows for distributed testing across a grid network using Watir.}
|
14
|
+
s.email = %q{tim.koops@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"lib/controller.rb",
|
26
|
+
"lib/extend.rb",
|
27
|
+
"lib/provider.rb",
|
28
|
+
"lib/watirgrid.rb",
|
29
|
+
"spec/spec.opts",
|
30
|
+
"spec/spec_helper.rb",
|
31
|
+
"spec/tuple_spec.rb",
|
32
|
+
"spec/watirgrid_spec.rb",
|
33
|
+
"watirgrid.gemspec"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/90kts/watirgrid}
|
36
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.5}
|
39
|
+
s.summary = %q{WatirGrid: Web Application Testing in Ruby across a grid network.}
|
40
|
+
s.test_files = [
|
41
|
+
"spec/spec_helper.rb",
|
42
|
+
"spec/tuple_spec.rb",
|
43
|
+
"spec/watirgrid_spec.rb"
|
44
|
+
]
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: watirgrid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Koopmans
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-11-16 00:00:00 +11:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.2.9
|
24
|
+
version:
|
25
|
+
description: WatirGrid allows for distributed testing across a grid network using Watir.
|
26
|
+
email: tim.koops@gmail.com
|
27
|
+
executables:
|
28
|
+
- controller
|
29
|
+
- provider
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files:
|
33
|
+
- LICENSE
|
34
|
+
- README.rdoc
|
35
|
+
files:
|
36
|
+
- .document
|
37
|
+
- .gitignore
|
38
|
+
- LICENSE
|
39
|
+
- NOTES.txt
|
40
|
+
- README.rdoc
|
41
|
+
- Rakefile
|
42
|
+
- TODO.txt
|
43
|
+
- bin/controller
|
44
|
+
- bin/provider
|
45
|
+
- lib/controller.rb
|
46
|
+
- lib/provider.rb
|
47
|
+
- lib/watirgrid.rb
|
48
|
+
- spec/grid_spec.rb
|
49
|
+
- spec/spec.opts
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
- spec/stub.rb
|
52
|
+
- spec/watirgrid_spec.rb
|
53
|
+
- watirgrid.gemspec
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/90kts/watirgrid
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options:
|
60
|
+
- --charset=UTF-8
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: "0"
|
68
|
+
version:
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.5
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: "WatirGrid: Web Application Testing in Ruby across a grid network."
|
82
|
+
test_files:
|
83
|
+
- spec/grid_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
- spec/stub.rb
|
86
|
+
- spec/watirgrid_spec.rb
|