poltergeist 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.
- data/CHANGELOG.md +0 -0
- data/LICENSE +20 -0
- data/README.md +85 -0
- data/lib/capybara/poltergeist/browser.rb +130 -0
- data/lib/capybara/poltergeist/client/agent.coffee +175 -0
- data/lib/capybara/poltergeist/client/browser.coffee +121 -0
- data/lib/capybara/poltergeist/client/compiled/agent.js +198 -0
- data/lib/capybara/poltergeist/client/compiled/browser.js +117 -0
- data/lib/capybara/poltergeist/client/compiled/connection.js +20 -0
- data/lib/capybara/poltergeist/client/compiled/main.js +38 -0
- data/lib/capybara/poltergeist/client/compiled/node.js +74 -0
- data/lib/capybara/poltergeist/client/compiled/web_page.js +136 -0
- data/lib/capybara/poltergeist/client/connection.coffee +11 -0
- data/lib/capybara/poltergeist/client/main.coffee +27 -0
- data/lib/capybara/poltergeist/client/node.coffee +56 -0
- data/lib/capybara/poltergeist/client/web_page.coffee +102 -0
- data/lib/capybara/poltergeist/client.rb +57 -0
- data/lib/capybara/poltergeist/driver.rb +85 -0
- data/lib/capybara/poltergeist/errors.rb +26 -0
- data/lib/capybara/poltergeist/node.rb +92 -0
- data/lib/capybara/poltergeist/server.rb +35 -0
- data/lib/capybara/poltergeist/server_manager.rb +118 -0
- data/lib/capybara/poltergeist/version.rb +5 -0
- data/lib/capybara/poltergeist.rb +20 -0
- metadata +102 -0
@@ -0,0 +1,102 @@
|
|
1
|
+
class Poltergeist.WebPage
|
2
|
+
@CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized',
|
3
|
+
'onLoadStarted', 'onResourceRequested', 'onResourceReceived']
|
4
|
+
@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render']
|
5
|
+
@COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize']
|
6
|
+
|
7
|
+
constructor: ->
|
8
|
+
@native = require('webpage').create()
|
9
|
+
@nodes = {}
|
10
|
+
@_source = ""
|
11
|
+
|
12
|
+
for callback in WebPage.CALLBACKS
|
13
|
+
this.bindCallback(callback)
|
14
|
+
|
15
|
+
this.injectAgent()
|
16
|
+
|
17
|
+
for command in @COMMANDS
|
18
|
+
do (command) =>
|
19
|
+
this.prototype[command] =
|
20
|
+
(arguments...) -> this.runCommand(command, arguments)
|
21
|
+
|
22
|
+
for delegate in @DELEGATES
|
23
|
+
do (delegate) =>
|
24
|
+
this.prototype[delegate] =
|
25
|
+
-> @native[delegate].apply(@native, arguments)
|
26
|
+
|
27
|
+
onInitializedNative: ->
|
28
|
+
@_source = null
|
29
|
+
this.injectAgent()
|
30
|
+
this.setScrollPosition({ left: 0, top: 0})
|
31
|
+
|
32
|
+
injectAgent: ->
|
33
|
+
if this.evaluate(-> typeof __poltergeist) == "undefined"
|
34
|
+
@native.injectJs('agent.js')
|
35
|
+
|
36
|
+
onConsoleMessageNative: (message) ->
|
37
|
+
if message == '__DOMContentLoaded'
|
38
|
+
@_source = @native.content
|
39
|
+
false
|
40
|
+
|
41
|
+
onLoadFinishedNative: ->
|
42
|
+
@_source or= @native.content
|
43
|
+
|
44
|
+
onConsoleMessage: (message) ->
|
45
|
+
console.log(message)
|
46
|
+
|
47
|
+
content: ->
|
48
|
+
@native.content
|
49
|
+
|
50
|
+
source: ->
|
51
|
+
@_source
|
52
|
+
|
53
|
+
viewportSize: ->
|
54
|
+
@native.viewportSize
|
55
|
+
|
56
|
+
scrollPosition: ->
|
57
|
+
@native.scrollPosition
|
58
|
+
|
59
|
+
setScrollPosition: (pos) ->
|
60
|
+
@native.scrollPosition = pos
|
61
|
+
|
62
|
+
viewport: ->
|
63
|
+
scroll = this.scrollPosition()
|
64
|
+
size = this.viewportSize()
|
65
|
+
|
66
|
+
top: scroll.top, bottom: scroll.top + size.height,
|
67
|
+
left: scroll.left, right: scroll.left + size.width,
|
68
|
+
width: size.width, height: size.height
|
69
|
+
|
70
|
+
get: (id) ->
|
71
|
+
@nodes[id] or= new Poltergeist.Node(this, id)
|
72
|
+
|
73
|
+
evaluate: (fn, args...) ->
|
74
|
+
@native.evaluate("function() { return #{this.stringifyCall(fn, args)} }")
|
75
|
+
|
76
|
+
execute: (fn, args...) ->
|
77
|
+
@native.evaluate("function() { #{this.stringifyCall(fn, args)} }")
|
78
|
+
|
79
|
+
stringifyCall: (fn, args) ->
|
80
|
+
if args.length == 0
|
81
|
+
"(#{fn.toString()})()"
|
82
|
+
else
|
83
|
+
# The JSON.stringify happens twice because the second time we are essentially
|
84
|
+
# escaping the string.
|
85
|
+
"(#{fn.toString()}).apply(this, JSON.parse(#{JSON.stringify(JSON.stringify(args))}))"
|
86
|
+
|
87
|
+
# For some reason phantomjs seems to have trouble with doing 'fat arrow' binding here,
|
88
|
+
# hence the 'that' closure.
|
89
|
+
bindCallback: (name) ->
|
90
|
+
that = this
|
91
|
+
@native[name] = ->
|
92
|
+
if that[name + 'Native']? # For internal callbacks
|
93
|
+
result = that[name + 'Native'].apply(that, arguments)
|
94
|
+
|
95
|
+
if result != false && that[name]? # For externally set callbacks
|
96
|
+
that[name].apply(that, arguments)
|
97
|
+
|
98
|
+
runCommand: (name, arguments) ->
|
99
|
+
this.evaluate(
|
100
|
+
(name, arguments) -> __poltergeist[name].apply(__poltergeist, arguments),
|
101
|
+
name, arguments
|
102
|
+
)
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Capybara::Poltergeist
|
4
|
+
class Client
|
5
|
+
PHANTOM_SCRIPT = File.expand_path('../client/compiled/main.js', __FILE__)
|
6
|
+
|
7
|
+
attr_reader :pid, :port, :path
|
8
|
+
|
9
|
+
def initialize(port, path = nil)
|
10
|
+
@port = port
|
11
|
+
@path = path || 'phantomjs'
|
12
|
+
|
13
|
+
start
|
14
|
+
at_exit { stop }
|
15
|
+
end
|
16
|
+
|
17
|
+
def start
|
18
|
+
@pid = Process.fork do
|
19
|
+
Open3.popen3("#{path} #{PHANTOM_SCRIPT} #{port}") do |stdin, stdout, stderr|
|
20
|
+
loop do
|
21
|
+
select = IO.select([stdout, stderr])
|
22
|
+
stream = select.first.first
|
23
|
+
|
24
|
+
break if stream.eof?
|
25
|
+
|
26
|
+
if stream == stdout
|
27
|
+
STDOUT.puts stdout.readline
|
28
|
+
elsif stream == stderr
|
29
|
+
line = stderr.readline
|
30
|
+
|
31
|
+
# QtWebkit seems to throw this error all the time when using WebSockets, but
|
32
|
+
# it doesn't appear to actually stop anything working, so filter it out.
|
33
|
+
#
|
34
|
+
# This isn't the nicest solution I know :( Hopefully it will be fixed in
|
35
|
+
# QtWebkit (if you search for this string, you'll see it's been reported in
|
36
|
+
# various places).
|
37
|
+
unless line.include?('WebCore::SocketStreamHandlePrivate::socketSentData()')
|
38
|
+
STDERR.puts line
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def stop
|
47
|
+
Process.kill('TERM', pid)
|
48
|
+
rescue Errno::ESRCH
|
49
|
+
# Bovvered, I ain't
|
50
|
+
end
|
51
|
+
|
52
|
+
def restart
|
53
|
+
stop
|
54
|
+
start
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Capybara::Poltergeist
|
2
|
+
class Driver < Capybara::Driver::Base
|
3
|
+
attr_reader :app, :server, :browser, :options
|
4
|
+
|
5
|
+
def initialize(app, options = {})
|
6
|
+
@app = app
|
7
|
+
@options = options
|
8
|
+
@server = Capybara::Server.new(app)
|
9
|
+
@browser = nil
|
10
|
+
|
11
|
+
@server.boot if Capybara.run_server
|
12
|
+
end
|
13
|
+
|
14
|
+
def browser
|
15
|
+
@browser ||= Browser.new(
|
16
|
+
:logger => logger,
|
17
|
+
:phantomjs => options[:phantomjs]
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def restart
|
22
|
+
browser.restart
|
23
|
+
end
|
24
|
+
|
25
|
+
# logger should be an object that responds to puts, or nil
|
26
|
+
def logger
|
27
|
+
options[:logger] || (options[:debug] && STDERR)
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit(path, attributes = {})
|
31
|
+
browser.visit(url(path), attributes)
|
32
|
+
end
|
33
|
+
|
34
|
+
def current_url
|
35
|
+
browser.current_url
|
36
|
+
end
|
37
|
+
|
38
|
+
def body
|
39
|
+
browser.body
|
40
|
+
end
|
41
|
+
|
42
|
+
def source
|
43
|
+
browser.source
|
44
|
+
end
|
45
|
+
|
46
|
+
def find(selector)
|
47
|
+
browser.find(selector).map { |node| Capybara::Poltergeist::Node.new(self, node) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def evaluate_script(script)
|
51
|
+
browser.evaluate(script)
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute_script(script)
|
55
|
+
browser.execute(script)
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def within_frame(id, &block)
|
60
|
+
browser.within_frame(id, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def reset!
|
64
|
+
browser.reset
|
65
|
+
end
|
66
|
+
|
67
|
+
def render(path)
|
68
|
+
browser.render(path)
|
69
|
+
end
|
70
|
+
|
71
|
+
def wait?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def invalid_element_errors
|
76
|
+
[Capybara::Poltergeist::ObsoleteNode]
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def url(path)
|
82
|
+
server.url(path)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Capybara
|
2
|
+
module Poltergeist
|
3
|
+
class Error < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class BrowserError < Error
|
7
|
+
attr_reader :text
|
8
|
+
|
9
|
+
def initialize(text)
|
10
|
+
@text = text
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
"Received error from PhantomJS client: #{text}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class ObsoleteNode < Error
|
19
|
+
attr_reader :node
|
20
|
+
|
21
|
+
def initialize(node)
|
22
|
+
@node = node
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Capybara::Poltergeist
|
2
|
+
class Node < Capybara::Driver::Node
|
3
|
+
alias id native
|
4
|
+
|
5
|
+
def browser
|
6
|
+
driver.browser
|
7
|
+
end
|
8
|
+
|
9
|
+
def command(name, *args)
|
10
|
+
browser.send(name, id, *args)
|
11
|
+
rescue BrowserError => error
|
12
|
+
if error.text == 'Poltergeist.ObsoleteNode'
|
13
|
+
raise ObsoleteNode.new(self)
|
14
|
+
else
|
15
|
+
raise error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(selector)
|
20
|
+
browser.find(selector, id).map { |node| self.class.new(driver, node) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def text
|
24
|
+
command :text
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](name)
|
28
|
+
command :attribute, name
|
29
|
+
end
|
30
|
+
|
31
|
+
def value
|
32
|
+
command :value
|
33
|
+
end
|
34
|
+
|
35
|
+
def set(value)
|
36
|
+
if tag_name == 'input'
|
37
|
+
type = self[:type]
|
38
|
+
|
39
|
+
if type == 'radio'
|
40
|
+
click
|
41
|
+
elsif type == 'checkbox'
|
42
|
+
if value && !checked? || !value && checked?
|
43
|
+
click
|
44
|
+
end
|
45
|
+
elsif type == 'file'
|
46
|
+
command :select_file, value
|
47
|
+
else
|
48
|
+
command :set, value
|
49
|
+
end
|
50
|
+
elsif tag_name == 'textarea'
|
51
|
+
command :set, value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def select_option
|
56
|
+
command :select, true
|
57
|
+
end
|
58
|
+
|
59
|
+
def unselect_option
|
60
|
+
command(:select, false) or
|
61
|
+
raise(Capybara::UnselectNotAllowed, "Cannot unselect option from single select box.")
|
62
|
+
end
|
63
|
+
|
64
|
+
def tag_name
|
65
|
+
@tag_name ||= command(:tag_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
def visible?
|
69
|
+
command :visible?
|
70
|
+
end
|
71
|
+
|
72
|
+
def checked?
|
73
|
+
self[:checked]
|
74
|
+
end
|
75
|
+
|
76
|
+
def selected?
|
77
|
+
self[:selected]
|
78
|
+
end
|
79
|
+
|
80
|
+
def click
|
81
|
+
command :click
|
82
|
+
end
|
83
|
+
|
84
|
+
def drag_to(other)
|
85
|
+
command :drag, other.id
|
86
|
+
end
|
87
|
+
|
88
|
+
def trigger(event)
|
89
|
+
command :trigger, event
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Capybara::Poltergeist
|
2
|
+
class Server
|
3
|
+
attr_reader :port
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@port = find_available_port
|
7
|
+
start
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
server_manager.start(port)
|
12
|
+
end
|
13
|
+
|
14
|
+
def restart
|
15
|
+
server_manager.restart(port)
|
16
|
+
end
|
17
|
+
|
18
|
+
def send(message)
|
19
|
+
server_manager.send(port, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def server_manager
|
25
|
+
ServerManager.instance
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_available_port
|
29
|
+
server = TCPServer.new('127.0.0.1', 0)
|
30
|
+
server.addr[1]
|
31
|
+
ensure
|
32
|
+
server.close if server
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'em-websocket'
|
2
|
+
require 'timeout'
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module Capybara::Poltergeist
|
6
|
+
# The reason for the lolzy thread code is because the EM reactor blocks the thread, so
|
7
|
+
# we have to put it in its own thread.
|
8
|
+
#
|
9
|
+
# The reason we are using EM, is because it has a WebSocket library. If there's a decent
|
10
|
+
# WebSocket library that doesn't require an event loop, we can use that.
|
11
|
+
class ServerManager
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
TIMEOUT = 30
|
15
|
+
|
16
|
+
class TimeoutError < StandardError
|
17
|
+
def initialize(message)
|
18
|
+
super "Server timed out waiting for response to #{@message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :sockets
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@instruction = nil
|
26
|
+
@response = nil
|
27
|
+
@sockets = {}
|
28
|
+
@waiting = false
|
29
|
+
|
30
|
+
@main = Thread.current
|
31
|
+
@thread = Thread.new { start_event_loop }
|
32
|
+
@thread.abort_on_exception = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def start(port)
|
36
|
+
thread_execute { start_websocket_server(port) }
|
37
|
+
end
|
38
|
+
|
39
|
+
# This isn't a 'proper' restart. It's more like 'wait for the client to connect again'.
|
40
|
+
def restart(port)
|
41
|
+
sockets[port] = nil
|
42
|
+
@thread.run
|
43
|
+
end
|
44
|
+
|
45
|
+
def send(port, message)
|
46
|
+
@message = nil
|
47
|
+
|
48
|
+
Timeout.timeout(TIMEOUT, TimeoutError.new(message)) do
|
49
|
+
# Ensure there is a socket before trying to send a message on it.
|
50
|
+
Thread.pass until sockets[port]
|
51
|
+
|
52
|
+
# Send the message
|
53
|
+
thread_execute { sockets[port].send(message) }
|
54
|
+
|
55
|
+
# Wait for the response message
|
56
|
+
Thread.pass until @message
|
57
|
+
end
|
58
|
+
|
59
|
+
@message
|
60
|
+
end
|
61
|
+
|
62
|
+
def thread_execute(&instruction)
|
63
|
+
# Ensure that the thread is waiting for an instruction before we wake it up
|
64
|
+
# to receive the instruction
|
65
|
+
Thread.pass until @waiting
|
66
|
+
|
67
|
+
@instruction = instruction
|
68
|
+
@waiting = false
|
69
|
+
|
70
|
+
# Bring the EM thread out of its sleep so that it can execute the instruction.
|
71
|
+
@thread.run
|
72
|
+
end
|
73
|
+
|
74
|
+
def start_event_loop
|
75
|
+
EM.run { await_instruction }
|
76
|
+
end
|
77
|
+
|
78
|
+
def start_websocket_server(port)
|
79
|
+
EventMachine.start_server('127.0.0.1', port, EventMachine::WebSocket::Connection, {}) do |socket|
|
80
|
+
socket.onopen do
|
81
|
+
connection_opened(port, socket)
|
82
|
+
end
|
83
|
+
|
84
|
+
socket.onmessage do |message|
|
85
|
+
message_received(message)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def connection_opened(port, socket)
|
91
|
+
sockets[port] = socket
|
92
|
+
await_instruction
|
93
|
+
end
|
94
|
+
|
95
|
+
def message_received(message)
|
96
|
+
@message = message
|
97
|
+
await_instruction
|
98
|
+
end
|
99
|
+
|
100
|
+
# Stop the thread so that it can be manually scheduled by the parent once there is
|
101
|
+
# something to do
|
102
|
+
def await_instruction
|
103
|
+
# Sleep this thread. The main thread will wake us up when there is an instruction
|
104
|
+
# to perform.
|
105
|
+
@waiting = true
|
106
|
+
Thread.stop
|
107
|
+
|
108
|
+
# Main thread has woken us up, so execute the current instruction.
|
109
|
+
if @instruction
|
110
|
+
@instruction.call
|
111
|
+
@instruction = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Continue execution of the thread until a socket callback fires, which will
|
115
|
+
# trigger then method again and send us back to sleep.
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'capybara'
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Poltergeist
|
5
|
+
autoload :Driver, 'capybara/poltergeist/driver'
|
6
|
+
autoload :Browser, 'capybara/poltergeist/browser'
|
7
|
+
autoload :Node, 'capybara/poltergeist/node'
|
8
|
+
autoload :ServerManager, 'capybara/poltergeist/server_manager'
|
9
|
+
autoload :Server, 'capybara/poltergeist/server'
|
10
|
+
autoload :Client, 'capybara/poltergeist/client'
|
11
|
+
|
12
|
+
autoload :Error, 'capybara/poltergeist/errors'
|
13
|
+
autoload :BrowserError, 'capybara/poltergeist/errors'
|
14
|
+
autoload :ObsoleteNode, 'capybara/poltergeist/errors'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
Capybara.register_driver :poltergeist do |app|
|
19
|
+
Capybara::Poltergeist::Driver.new(app)
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: poltergeist
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jon Leighton
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-27 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: capybara
|
16
|
+
requirement: &20250700 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *20250700
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: em-websocket
|
27
|
+
requirement: &20220020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.3.1
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *20220020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
requirement: &20216800 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.6'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *20216800
|
47
|
+
description: PhantomJS driver for Capybara
|
48
|
+
email:
|
49
|
+
- j@jonathanleighton.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- lib/capybara/poltergeist.rb
|
55
|
+
- lib/capybara/poltergeist/client.rb
|
56
|
+
- lib/capybara/poltergeist/server.rb
|
57
|
+
- lib/capybara/poltergeist/browser.rb
|
58
|
+
- lib/capybara/poltergeist/errors.rb
|
59
|
+
- lib/capybara/poltergeist/version.rb
|
60
|
+
- lib/capybara/poltergeist/driver.rb
|
61
|
+
- lib/capybara/poltergeist/client/connection.coffee
|
62
|
+
- lib/capybara/poltergeist/client/node.coffee
|
63
|
+
- lib/capybara/poltergeist/client/compiled/agent.js
|
64
|
+
- lib/capybara/poltergeist/client/compiled/browser.js
|
65
|
+
- lib/capybara/poltergeist/client/compiled/main.js
|
66
|
+
- lib/capybara/poltergeist/client/compiled/node.js
|
67
|
+
- lib/capybara/poltergeist/client/compiled/connection.js
|
68
|
+
- lib/capybara/poltergeist/client/compiled/web_page.js
|
69
|
+
- lib/capybara/poltergeist/client/main.coffee
|
70
|
+
- lib/capybara/poltergeist/client/web_page.coffee
|
71
|
+
- lib/capybara/poltergeist/client/browser.coffee
|
72
|
+
- lib/capybara/poltergeist/client/agent.coffee
|
73
|
+
- lib/capybara/poltergeist/server_manager.rb
|
74
|
+
- lib/capybara/poltergeist/node.rb
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- CHANGELOG.md
|
78
|
+
homepage: http://github.com/jonleighton/poltergeist
|
79
|
+
licenses: []
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 1.8.6
|
99
|
+
signing_key:
|
100
|
+
specification_version: 3
|
101
|
+
summary: PhantomJS driver for Capybara
|
102
|
+
test_files: []
|