poltergeist 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|