cukeforker-webdriver 0.0.1
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/.gitignore +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +66 -0
- data/Rakefile +5 -0
- data/cukeforker-webdriver.gemspec +36 -0
- data/lib/cukeforker-webdriver.rb +4 -0
- data/lib/cukeforker-webdriver/capybara/driver.rb +26 -0
- data/lib/cukeforker-webdriver/no_lock_firefox_bridge.rb +33 -0
- data/lib/cukeforker-webdriver/parallel_port_manager.rb +28 -0
- data/lib/cukeforker-webdriver/port_pool.rb +150 -0
- data/lib/cukeforker-webdriver/runner.rb +15 -0
- metadata +183 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
cukeforker-webdriver (0.0.1)
|
5
|
+
capybara
|
6
|
+
childprocess
|
7
|
+
cucumber
|
8
|
+
gherkin
|
9
|
+
rspec
|
10
|
+
selenium-webdriver
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: http://rubygems.org/
|
14
|
+
specs:
|
15
|
+
builder (3.0.0)
|
16
|
+
capybara (1.1.1)
|
17
|
+
mime-types (>= 1.16)
|
18
|
+
nokogiri (>= 1.3.3)
|
19
|
+
rack (>= 1.0.0)
|
20
|
+
rack-test (>= 0.5.4)
|
21
|
+
selenium-webdriver (~> 2.0)
|
22
|
+
xpath (~> 0.1.4)
|
23
|
+
childprocess (0.2.2)
|
24
|
+
ffi (~> 1.0.6)
|
25
|
+
cucumber (1.0.6)
|
26
|
+
builder (>= 2.1.2)
|
27
|
+
diff-lcs (>= 1.1.2)
|
28
|
+
gherkin (~> 2.4.18)
|
29
|
+
json (>= 1.4.6)
|
30
|
+
term-ansicolor (>= 1.0.6)
|
31
|
+
diff-lcs (1.1.3)
|
32
|
+
ffi (1.0.9)
|
33
|
+
gherkin (2.4.21)
|
34
|
+
json (>= 1.4.6)
|
35
|
+
json (1.5.4)
|
36
|
+
json_pure (1.6.1)
|
37
|
+
mime-types (1.16)
|
38
|
+
nokogiri (1.5.0)
|
39
|
+
rack (1.3.3)
|
40
|
+
rack-test (0.6.1)
|
41
|
+
rack (>= 1.0)
|
42
|
+
rake (0.9.2)
|
43
|
+
rspec (2.6.0)
|
44
|
+
rspec-core (~> 2.6.0)
|
45
|
+
rspec-expectations (~> 2.6.0)
|
46
|
+
rspec-mocks (~> 2.6.0)
|
47
|
+
rspec-core (2.6.4)
|
48
|
+
rspec-expectations (2.6.0)
|
49
|
+
diff-lcs (~> 1.1.2)
|
50
|
+
rspec-mocks (2.6.0)
|
51
|
+
rubyzip (0.9.4)
|
52
|
+
selenium-webdriver (2.6.0)
|
53
|
+
childprocess (>= 0.2.1)
|
54
|
+
ffi (>= 1.0.7)
|
55
|
+
json_pure
|
56
|
+
rubyzip
|
57
|
+
term-ansicolor (1.0.6)
|
58
|
+
xpath (0.1.4)
|
59
|
+
nokogiri (~> 1.3)
|
60
|
+
|
61
|
+
PLATFORMS
|
62
|
+
ruby
|
63
|
+
|
64
|
+
DEPENDENCIES
|
65
|
+
cukeforker-webdriver!
|
66
|
+
rake (>= 0.9.2)
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'cukeforker-webdriver'
|
6
|
+
s.version = '0.0.1'
|
7
|
+
s.authors = ["Derek Ekins"]
|
8
|
+
s.description = 'Run webdriver with ports pre allocated - thanks go to Jari Bakken for the original implementation https://gist.github.com/949241'
|
9
|
+
s.summary = "#{s.name}-#{s.version}"
|
10
|
+
s.email = 'derek@spathi.com'
|
11
|
+
s.homepage = "http://github.com/dereke/cukeforker-webdriver"
|
12
|
+
|
13
|
+
s.platform = Gem::Platform::RUBY
|
14
|
+
s.post_install_message = %{
|
15
|
+
(::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)
|
16
|
+
|
17
|
+
Thank you for installing Cukeforker Web Driver extensions
|
18
|
+
|
19
|
+
(::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)
|
20
|
+
|
21
|
+
}
|
22
|
+
|
23
|
+
s.add_dependency 'rspec'
|
24
|
+
s.add_dependency 'cucumber'
|
25
|
+
s.add_dependency 'gherkin'
|
26
|
+
s.add_dependency 'capybara'
|
27
|
+
s.add_dependency 'selenium-webdriver'
|
28
|
+
s.add_dependency 'childprocess'
|
29
|
+
|
30
|
+
s.add_development_dependency 'rake', '>= 0.9.2'
|
31
|
+
|
32
|
+
s.rubygems_version = ">= 1.6.1"
|
33
|
+
s.files = `git ls-files`.split("\n")
|
34
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
35
|
+
s.require_path = "lib"
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
require_relative '../no_lock_firefox_bridge'
|
3
|
+
|
4
|
+
class Capybara::Selenium::Driver
|
5
|
+
def browser
|
6
|
+
unless @browser
|
7
|
+
@port = ENV['web_driver_port']
|
8
|
+
options[:port] = @port
|
9
|
+
@browser = Selenium::WebDriver::Driver.new(CukeForker::WebDriver::NoLockFirefoxBridge.new(options.reject { |key,val| SPECIAL_OPTIONS.include?(key)}))
|
10
|
+
|
11
|
+
main = Process.pid
|
12
|
+
at_exit do
|
13
|
+
quit if Process.pid == main
|
14
|
+
end
|
15
|
+
end
|
16
|
+
@browser
|
17
|
+
end
|
18
|
+
|
19
|
+
def quit
|
20
|
+
begin
|
21
|
+
@browser.quit
|
22
|
+
rescue Errno::ECONNREFUSED
|
23
|
+
ensure
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CukeForker
|
2
|
+
module WebDriver
|
3
|
+
class NoLockFirefoxBridge < Selenium::WebDriver::Firefox::Bridge
|
4
|
+
def initialize(*args)
|
5
|
+
super(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
class FakeLock
|
9
|
+
def initialize(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def locked
|
13
|
+
yield
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NoLockLauncher < Selenium::WebDriver::Firefox::Launcher
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_free_port
|
21
|
+
end
|
22
|
+
|
23
|
+
def socket_lock
|
24
|
+
@socket_lock ||= FakeLock.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_launcher(port, profile)
|
29
|
+
NoLockLauncher.new(Selenium::WebDriver::Firefox::Binary.new, port, profile)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'cukeforker/abstract_listener'
|
2
|
+
|
3
|
+
module CukeForker
|
4
|
+
module WebDriver
|
5
|
+
class ParallelPortManager < CukeForker::AbstractListener
|
6
|
+
def initialize(max)
|
7
|
+
@port_pool = CukeForker::WebDriver::PortPool.new max
|
8
|
+
@available_ports = []
|
9
|
+
until @available_ports.length == max
|
10
|
+
@available_ports << @port_pool.get
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_worker_starting(worker)
|
15
|
+
worker.data.web_driver_port = @available_ports.shift
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_worker_forked(worker)
|
19
|
+
require_relative 'capybara/driver'
|
20
|
+
ENV['web_driver_port'] = worker.data.web_driver_port.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_worker_finished(worker)
|
24
|
+
@available_ports << worker.data.web_driver_port
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'childprocess'
|
3
|
+
|
4
|
+
module CukeForker
|
5
|
+
module WebDriver
|
6
|
+
class PortPool
|
7
|
+
|
8
|
+
class TooManyPortsError < StandardError; end
|
9
|
+
class TimeoutError < StandardError; end
|
10
|
+
class BindError < StandardError; end
|
11
|
+
|
12
|
+
START_PORT = 1024
|
13
|
+
HOST = "127.0.0.1"
|
14
|
+
RELEASE_TIMEOUT = 20
|
15
|
+
PORT_INCREMENT = 2
|
16
|
+
|
17
|
+
def initialize(capacity)
|
18
|
+
@last_port = START_PORT - 1
|
19
|
+
@capacity = capacity
|
20
|
+
@servers = []
|
21
|
+
@running = []
|
22
|
+
|
23
|
+
bind
|
24
|
+
end
|
25
|
+
|
26
|
+
def get
|
27
|
+
if @servers.empty?
|
28
|
+
STDERR.puts "out of ports, finding next"
|
29
|
+
@servers << next_server
|
30
|
+
end
|
31
|
+
|
32
|
+
s = @servers.shift
|
33
|
+
port = s.addr.fetch(1)
|
34
|
+
s.close
|
35
|
+
|
36
|
+
wait_while_listening port
|
37
|
+
port
|
38
|
+
end
|
39
|
+
|
40
|
+
def release(port)
|
41
|
+
raise TooManyPortsError if @servers.size == @capacity
|
42
|
+
server = wait_for_server(port)
|
43
|
+
@servers << server
|
44
|
+
rescue WebDriverPortPool::TimeoutError
|
45
|
+
STDERR.puts "timed out while releasing #{port}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def stop
|
49
|
+
@servers.each { |s|
|
50
|
+
begin
|
51
|
+
s.close
|
52
|
+
rescue IOError
|
53
|
+
end
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def close_on_exec
|
58
|
+
STDERR.puts "#{self.class}: #{Process.pid} pool closed on exec"
|
59
|
+
@servers.each { |s| ChildProcess.close_on_exec s }
|
60
|
+
end
|
61
|
+
|
62
|
+
def size
|
63
|
+
@servers.size
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def bind
|
69
|
+
@servers << next_server until size == @capacity
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_server
|
73
|
+
max_ports = @capacity*100
|
74
|
+
upper_bound = START_PORT + max_ports
|
75
|
+
@last_port += PORT_INCREMENT
|
76
|
+
|
77
|
+
until server = try_bind(@last_port)
|
78
|
+
if @last_port > upper_bound
|
79
|
+
raise BindError, "unable to find free port within #{START_PORT}..#{upper_bound} tries, last port tried #{@last_port}"
|
80
|
+
end
|
81
|
+
|
82
|
+
@last_port += PORT_INCREMENT
|
83
|
+
end
|
84
|
+
|
85
|
+
server
|
86
|
+
end
|
87
|
+
|
88
|
+
def try_bind(port)
|
89
|
+
server_for port
|
90
|
+
rescue SocketError, Errno::EADDRINUSE
|
91
|
+
# ok
|
92
|
+
end
|
93
|
+
|
94
|
+
def server_for(port)
|
95
|
+
s = TCPServer.new(HOST, port)
|
96
|
+
ChildProcess.close_on_exec s # make sure browsers don't inherit the file descriptors
|
97
|
+
s
|
98
|
+
end
|
99
|
+
|
100
|
+
def wait_for_server(port)
|
101
|
+
wait_until(RELEASE_TIMEOUT) {
|
102
|
+
res = try_bind port
|
103
|
+
# p `lsof -i TCP:#{port}` unless res
|
104
|
+
|
105
|
+
res
|
106
|
+
}
|
107
|
+
end
|
108
|
+
|
109
|
+
def wait_while_listening(port)
|
110
|
+
wait_until(RELEASE_TIMEOUT) {
|
111
|
+
not listening?(port, 5)
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def wait_until(timeout, &blk)
|
116
|
+
max_time = Time.now + timeout
|
117
|
+
|
118
|
+
until res = yield
|
119
|
+
if Time.now >= max_time
|
120
|
+
raise TimeoutError, "timed out"
|
121
|
+
else
|
122
|
+
sleep 0.1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
res
|
127
|
+
end
|
128
|
+
|
129
|
+
def listening?(port, timeout = nil)
|
130
|
+
addr = Socket.getaddrinfo(HOST, nil)
|
131
|
+
sock = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
132
|
+
|
133
|
+
if timeout
|
134
|
+
secs = Integer(timeout)
|
135
|
+
usecs = Integer((timeout - secs) * 1_000_000)
|
136
|
+
optval = [secs, usecs].pack("l_2")
|
137
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
|
138
|
+
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval
|
139
|
+
end
|
140
|
+
sock.connect(Socket.pack_sockaddr_in(port, addr[0][3]))
|
141
|
+
sock.close
|
142
|
+
|
143
|
+
true
|
144
|
+
rescue => e
|
145
|
+
false
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module CukeForker
|
2
|
+
module WebDriver
|
3
|
+
class Runner
|
4
|
+
def self.run(features, options)
|
5
|
+
max = options.fetch(:max, 2)
|
6
|
+
options[:notify] = CukeForker::WebDriver::ParallelPortManager.new(max)
|
7
|
+
|
8
|
+
CukeForker::Runner.run(
|
9
|
+
features,
|
10
|
+
options
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cukeforker-webdriver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Derek Ekins
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-21 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: cucumber
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: gherkin
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: capybara
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
type: :runtime
|
76
|
+
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: selenium-webdriver
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
type: :runtime
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: childprocess
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 3
|
100
|
+
segments:
|
101
|
+
- 0
|
102
|
+
version: "0"
|
103
|
+
type: :runtime
|
104
|
+
version_requirements: *id006
|
105
|
+
- !ruby/object:Gem::Dependency
|
106
|
+
name: rake
|
107
|
+
prerelease: false
|
108
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
hash: 63
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
- 9
|
117
|
+
- 2
|
118
|
+
version: 0.9.2
|
119
|
+
type: :development
|
120
|
+
version_requirements: *id007
|
121
|
+
description: Run webdriver with ports pre allocated - thanks go to Jari Bakken for the original implementation https://gist.github.com/949241
|
122
|
+
email: derek@spathi.com
|
123
|
+
executables: []
|
124
|
+
|
125
|
+
extensions: []
|
126
|
+
|
127
|
+
extra_rdoc_files: []
|
128
|
+
|
129
|
+
files:
|
130
|
+
- .gitignore
|
131
|
+
- Gemfile
|
132
|
+
- Gemfile.lock
|
133
|
+
- Rakefile
|
134
|
+
- cukeforker-webdriver.gemspec
|
135
|
+
- lib/cukeforker-webdriver.rb
|
136
|
+
- lib/cukeforker-webdriver/capybara/driver.rb
|
137
|
+
- lib/cukeforker-webdriver/no_lock_firefox_bridge.rb
|
138
|
+
- lib/cukeforker-webdriver/parallel_port_manager.rb
|
139
|
+
- lib/cukeforker-webdriver/port_pool.rb
|
140
|
+
- lib/cukeforker-webdriver/runner.rb
|
141
|
+
has_rdoc: true
|
142
|
+
homepage: http://github.com/dereke/cukeforker-webdriver
|
143
|
+
licenses: []
|
144
|
+
|
145
|
+
post_install_message: |+
|
146
|
+
|
147
|
+
(::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)
|
148
|
+
|
149
|
+
Thank you for installing Cukeforker Web Driver extensions
|
150
|
+
|
151
|
+
(::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)
|
152
|
+
|
153
|
+
rdoc_options: []
|
154
|
+
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
hash: 3
|
163
|
+
segments:
|
164
|
+
- 0
|
165
|
+
version: "0"
|
166
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
167
|
+
none: false
|
168
|
+
requirements:
|
169
|
+
- - ">="
|
170
|
+
- !ruby/object:Gem::Version
|
171
|
+
hash: 3
|
172
|
+
segments:
|
173
|
+
- 0
|
174
|
+
version: "0"
|
175
|
+
requirements: []
|
176
|
+
|
177
|
+
rubyforge_project:
|
178
|
+
rubygems_version: 1.6.2
|
179
|
+
signing_key:
|
180
|
+
specification_version: 3
|
181
|
+
summary: cukeforker-webdriver-0.0.1
|
182
|
+
test_files: []
|
183
|
+
|