poltergeist 1.7.0 → 1.8.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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/capybara/poltergeist/browser.rb +19 -8
- data/lib/capybara/poltergeist/client/agent.coffee +37 -12
- data/lib/capybara/poltergeist/client/browser.coffee +107 -90
- data/lib/capybara/poltergeist/client/cmd.coffee +17 -0
- data/lib/capybara/poltergeist/client/compiled/agent.js +97 -71
- data/lib/capybara/poltergeist/client/compiled/browser.js +204 -173
- data/lib/capybara/poltergeist/client/compiled/cmd.js +31 -0
- data/lib/capybara/poltergeist/client/compiled/connection.js +2 -2
- data/lib/capybara/poltergeist/client/compiled/main.js +45 -43
- data/lib/capybara/poltergeist/client/compiled/node.js +10 -11
- data/lib/capybara/poltergeist/client/compiled/web_page.js +89 -60
- data/lib/capybara/poltergeist/client/main.coffee +12 -9
- data/lib/capybara/poltergeist/client/node.coffee +6 -3
- data/lib/capybara/poltergeist/client/web_page.coffee +22 -8
- data/lib/capybara/poltergeist/command.rb +17 -0
- data/lib/capybara/poltergeist/driver.rb +40 -4
- data/lib/capybara/poltergeist/errors.rb +5 -1
- data/lib/capybara/poltergeist/inspector.rb +5 -5
- data/lib/capybara/poltergeist/node.rb +16 -1
- data/lib/capybara/poltergeist/server.rb +2 -2
- data/lib/capybara/poltergeist/version.rb +1 -1
- data/lib/capybara/poltergeist/web_socket_server.rb +23 -12
- metadata +11 -8
@@ -1,6 +1,6 @@
|
|
1
1
|
class Poltergeist
|
2
2
|
constructor: (port, width, height) ->
|
3
|
-
@browser = new Poltergeist.Browser(
|
3
|
+
@browser = new Poltergeist.Browser(width, height)
|
4
4
|
@connection = new Poltergeist.Connection(this, port)
|
5
5
|
|
6
6
|
# The QtWebKit bridge doesn't seem to like Function.prototype.bind
|
@@ -11,20 +11,21 @@ class Poltergeist
|
|
11
11
|
|
12
12
|
runCommand: (command) ->
|
13
13
|
@running = true
|
14
|
-
|
14
|
+
command = new Poltergeist.Cmd(this, command.id, command.name, command.args)
|
15
15
|
try
|
16
|
-
@browser
|
16
|
+
command.run(@browser)
|
17
17
|
catch error
|
18
18
|
if error instanceof Poltergeist.Error
|
19
|
-
this.sendError(error)
|
19
|
+
this.sendError(command.id, error)
|
20
20
|
else
|
21
|
-
this.sendError(new Poltergeist.BrowserError(error.toString(), error.stack))
|
21
|
+
this.sendError(command.id, new Poltergeist.BrowserError(error.toString(), error.stack))
|
22
22
|
|
23
|
-
sendResponse: (response) ->
|
24
|
-
this.send(response: response)
|
23
|
+
sendResponse: (command_id, response) ->
|
24
|
+
this.send(command_id: command_id, response: response)
|
25
25
|
|
26
|
-
sendError: (error) ->
|
26
|
+
sendError: (command_id, error) ->
|
27
27
|
this.send(
|
28
|
+
command_id: command_id,
|
28
29
|
error:
|
29
30
|
name: error.name || 'Generic',
|
30
31
|
args: error.args && error.args() || [error.toString()]
|
@@ -76,8 +77,9 @@ class Poltergeist.BrowserError extends Poltergeist.Error
|
|
76
77
|
args: -> [@message, @stack]
|
77
78
|
|
78
79
|
class Poltergeist.StatusFailError extends Poltergeist.Error
|
80
|
+
constructor: (@url) ->
|
79
81
|
name: "Poltergeist.StatusFailError"
|
80
|
-
args: -> []
|
82
|
+
args: -> [@url]
|
81
83
|
|
82
84
|
class Poltergeist.NoSuchWindowError extends Poltergeist.Error
|
83
85
|
name: "Poltergeist.NoSuchWindowError"
|
@@ -88,6 +90,7 @@ class Poltergeist.NoSuchWindowError extends Poltergeist.Error
|
|
88
90
|
phantom.injectJs("#{phantom.libraryPath}/web_page.js")
|
89
91
|
phantom.injectJs("#{phantom.libraryPath}/node.js")
|
90
92
|
phantom.injectJs("#{phantom.libraryPath}/connection.js")
|
93
|
+
phantom.injectJs("#{phantom.libraryPath}/cmd.js")
|
91
94
|
phantom.injectJs("#{phantom.libraryPath}/browser.js")
|
92
95
|
|
93
96
|
system = require 'system'
|
@@ -3,8 +3,9 @@
|
|
3
3
|
class Poltergeist.Node
|
4
4
|
@DELEGATES = ['allText', 'visibleText', 'getAttribute', 'value', 'set', 'setAttribute', 'isObsolete',
|
5
5
|
'removeAttribute', 'isMultiple', 'select', 'tagName', 'find', 'getAttributes',
|
6
|
-
'isVisible', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest',
|
7
|
-
'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection',
|
6
|
+
'isVisible', 'isInViewport', 'position', 'trigger', 'parentId', 'parentIds', 'mouseEventTest',
|
7
|
+
'scrollIntoView', 'isDOMEqual', 'isDisabled', 'deleteText', 'containsSelection',
|
8
|
+
'path', 'getProperty']
|
8
9
|
|
9
10
|
constructor: (@page, @id) ->
|
10
11
|
|
@@ -31,7 +32,8 @@ class Poltergeist.Node
|
|
31
32
|
mouseEvent: (name) ->
|
32
33
|
this.scrollIntoView()
|
33
34
|
|
34
|
-
pos
|
35
|
+
pos = this.mouseEventPosition()
|
36
|
+
|
35
37
|
test = this.mouseEventTest(pos.x, pos.y)
|
36
38
|
|
37
39
|
if test.status == 'success'
|
@@ -68,3 +70,4 @@ class Poltergeist.Node
|
|
68
70
|
|
69
71
|
isEqual: (other) ->
|
70
72
|
@page == other.page && this.isDOMEqual(other.id)
|
73
|
+
|
@@ -86,7 +86,7 @@ class Poltergeist.WebPage
|
|
86
86
|
else
|
87
87
|
@lastRequestId = request.id
|
88
88
|
|
89
|
-
if request.url == @redirectURL
|
89
|
+
if @normalizeURL(request.url) == @redirectURL
|
90
90
|
@redirectURL = null
|
91
91
|
@requestId = request.id
|
92
92
|
|
@@ -100,7 +100,7 @@ class Poltergeist.WebPage
|
|
100
100
|
|
101
101
|
if @requestId == response.id
|
102
102
|
if response.redirectURL
|
103
|
-
@redirectURL = response.redirectURL
|
103
|
+
@redirectURL = @normalizeURL(response.redirectURL)
|
104
104
|
else
|
105
105
|
@statusCode = response.status
|
106
106
|
@_responseHeaders = response.headers
|
@@ -164,10 +164,10 @@ class Poltergeist.WebPage
|
|
164
164
|
title: ->
|
165
165
|
this.native().frameTitle
|
166
166
|
|
167
|
-
frameUrl: (
|
168
|
-
query = (
|
169
|
-
document.querySelector("iframe[name='#{
|
170
|
-
this.evaluate(query,
|
167
|
+
frameUrl: (frameNameOrId) ->
|
168
|
+
query = (frameNameOrId) ->
|
169
|
+
document.querySelector("iframe[name='#{frameNameOrId}'], iframe[id='#{frameNameOrId}']")?.src
|
170
|
+
this.evaluate(query, frameNameOrId)
|
171
171
|
|
172
172
|
clearErrors: ->
|
173
173
|
@errors = []
|
@@ -238,7 +238,16 @@ class Poltergeist.WebPage
|
|
238
238
|
@frames.push(name)
|
239
239
|
true
|
240
240
|
else
|
241
|
-
|
241
|
+
frame_no = this.native().evaluate(
|
242
|
+
(frame_name) ->
|
243
|
+
frames = document.querySelectorAll("iframe, frame")
|
244
|
+
(idx for f, idx in frames when f?['name'] == frame_name or f?['id'] == frame_name)[0]
|
245
|
+
, name)
|
246
|
+
if frame_no? and this.native().switchToFrame(frame_no)
|
247
|
+
@frames.push(name)
|
248
|
+
true
|
249
|
+
else
|
250
|
+
false
|
242
251
|
|
243
252
|
popFrame: ->
|
244
253
|
@frames.pop()
|
@@ -299,7 +308,7 @@ class Poltergeist.WebPage
|
|
299
308
|
else
|
300
309
|
# The JSON.stringify happens twice because the second time we are essentially
|
301
310
|
# escaping the string.
|
302
|
-
"(#{fn.toString()}).apply(this, JSON.parse(#{JSON.stringify(JSON.stringify(args))}))"
|
311
|
+
"(#{fn.toString()}).apply(this, PoltergeistAgent.JSON.parse(#{JSON.stringify(JSON.stringify(args))}))"
|
303
312
|
|
304
313
|
# For some reason phantomjs seems to have trouble with doing 'fat arrow' binding here,
|
305
314
|
# hence the 'that' closure.
|
@@ -339,3 +348,8 @@ class Poltergeist.WebPage
|
|
339
348
|
|
340
349
|
canGoForward: ->
|
341
350
|
this.native().canGoForward
|
351
|
+
|
352
|
+
normalizeURL: (url) ->
|
353
|
+
parser = document.createElement('a')
|
354
|
+
parser.href = url
|
355
|
+
return parser.href
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Capybara::Poltergeist
|
4
|
+
class Command
|
5
|
+
attr_reader :id
|
6
|
+
|
7
|
+
def initialize(name, *args)
|
8
|
+
@id = SecureRandom.uuid
|
9
|
+
@name = name
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
JSON.dump({ 'id' => @id, 'name' => @name, 'args' => @args })
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -200,6 +200,18 @@ module Capybara::Poltergeist
|
|
200
200
|
end
|
201
201
|
alias_method :resize_window, :resize
|
202
202
|
|
203
|
+
def resize_window_to(handle, width, height)
|
204
|
+
within_window(handle) do
|
205
|
+
resize(width, height)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def window_size(handle)
|
210
|
+
within_window(handle) do
|
211
|
+
evaluate_script('[window.innerWidth, window.innerHeight]')
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
203
215
|
def scroll_to(left, top)
|
204
216
|
browser.scroll_to(left, top)
|
205
217
|
end
|
@@ -244,7 +256,7 @@ module Capybara::Poltergeist
|
|
244
256
|
if @started
|
245
257
|
URI.parse(browser.current_url).host
|
246
258
|
else
|
247
|
-
Capybara.app_host || "127.0.0.1"
|
259
|
+
URI.parse(Capybara.app_host || '').host || "127.0.0.1"
|
248
260
|
end
|
249
261
|
end
|
250
262
|
|
@@ -276,7 +288,10 @@ module Capybara::Poltergeist
|
|
276
288
|
|
277
289
|
def debug
|
278
290
|
if @options[:inspector]
|
279
|
-
|
291
|
+
# Fall back to default scheme
|
292
|
+
scheme = URI.parse(browser.current_url).scheme rescue nil
|
293
|
+
scheme = 'http' if scheme != 'https'
|
294
|
+
inspector.open(scheme)
|
280
295
|
pause
|
281
296
|
else
|
282
297
|
raise Error, "To use the remote debugging, you have to launch the driver " \
|
@@ -285,8 +300,29 @@ module Capybara::Poltergeist
|
|
285
300
|
end
|
286
301
|
|
287
302
|
def pause
|
288
|
-
|
289
|
-
|
303
|
+
# STDIN is not necessarily connected to a keyboard. It might even be closed.
|
304
|
+
# So we need a method other than keypress to continue.
|
305
|
+
|
306
|
+
# In jRuby - STDIN returns immediately from select
|
307
|
+
# see https://github.com/jruby/jruby/issues/1783
|
308
|
+
read, write = IO.pipe
|
309
|
+
Thread.new { IO.copy_stream(STDIN, write); write.close }
|
310
|
+
|
311
|
+
STDERR.puts "Poltergeist execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue."
|
312
|
+
|
313
|
+
signal = false
|
314
|
+
old_trap = trap('SIGCONT') { signal = true; STDERR.puts "\nSignal SIGCONT received" }
|
315
|
+
keyboard = IO.select([read], nil, nil, 1) until keyboard || signal # wait for data on STDIN or signal SIGCONT received
|
316
|
+
|
317
|
+
begin
|
318
|
+
input = read.read_nonblock(80) # clear out the read buffer
|
319
|
+
puts unless input && input =~ /\n\z/
|
320
|
+
rescue EOFError, IO::WaitReadable # Ignore problems reading from STDIN.
|
321
|
+
end unless signal
|
322
|
+
|
323
|
+
trap('SIGCONT', old_trap) # Restore the previuos signal handler, if there was one.
|
324
|
+
|
325
|
+
STDERR.puts 'Continuing'
|
290
326
|
end
|
291
327
|
|
292
328
|
def wait?
|
@@ -55,8 +55,12 @@ module Capybara
|
|
55
55
|
end
|
56
56
|
|
57
57
|
class StatusFailError < ClientError
|
58
|
+
def url
|
59
|
+
response['args'].first
|
60
|
+
end
|
61
|
+
|
58
62
|
def message
|
59
|
-
"Request failed to reach server, check DNS and/or server status"
|
63
|
+
"Request to '#{url}' failed to reach server, check DNS and/or server status"
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
@@ -18,15 +18,15 @@ module Capybara::Poltergeist
|
|
18
18
|
@browser ||= self.class.detect_browser
|
19
19
|
end
|
20
20
|
|
21
|
-
def url
|
22
|
-
"
|
21
|
+
def url(scheme)
|
22
|
+
"#{scheme}://localhost:#{port}/"
|
23
23
|
end
|
24
24
|
|
25
|
-
def open
|
25
|
+
def open(scheme)
|
26
26
|
if browser
|
27
|
-
Process.spawn(browser, url)
|
27
|
+
Process.spawn(browser, url(scheme))
|
28
28
|
else
|
29
|
-
raise Error, "Could not find a browser executable to open #{url}. " \
|
29
|
+
raise Error, "Could not find a browser executable to open #{url(scheme)}. " \
|
30
30
|
"You can specify one manually using e.g. `:inspector => 'chromium'` " \
|
31
31
|
"as a configuration option for Poltergeist."
|
32
32
|
end
|
@@ -50,8 +50,23 @@ module Capybara::Poltergeist
|
|
50
50
|
filter_text command(:visible_text)
|
51
51
|
end
|
52
52
|
|
53
|
+
def property(name)
|
54
|
+
command :property, name
|
55
|
+
end
|
56
|
+
|
53
57
|
def [](name)
|
54
|
-
|
58
|
+
# Although the attribute matters, the property is consistent. Return that in
|
59
|
+
# preference to the attribute for links and images.
|
60
|
+
if (tag_name == 'img' and name == 'src') or (tag_name == 'a' and name == 'href' )
|
61
|
+
#if attribute exists get the property
|
62
|
+
value = command(:attribute, name) && command(:property, name)
|
63
|
+
return value
|
64
|
+
end
|
65
|
+
|
66
|
+
value = property(name)
|
67
|
+
value = command(:attribute, name) if value.nil? || value.is_a?(Hash)
|
68
|
+
|
69
|
+
value
|
55
70
|
end
|
56
71
|
|
57
72
|
def attributes
|
@@ -24,6 +24,7 @@ module Capybara::Poltergeist
|
|
24
24
|
def initialize(port = nil, timeout = nil)
|
25
25
|
@timeout = timeout
|
26
26
|
@server = start_server(port)
|
27
|
+
@receive_mutex = Mutex.new
|
27
28
|
end
|
28
29
|
|
29
30
|
def start_server(port)
|
@@ -51,11 +52,14 @@ module Capybara::Poltergeist
|
|
51
52
|
# and use that to initialize a Web Socket.
|
52
53
|
def accept
|
53
54
|
@socket = server.accept
|
54
|
-
@messages =
|
55
|
+
@messages = {}
|
55
56
|
|
56
57
|
@driver = ::WebSocket::Driver.server(self)
|
57
58
|
@driver.on(:connect) { |event| @driver.start }
|
58
|
-
@driver.on(:message)
|
59
|
+
@driver.on(:message) do |event|
|
60
|
+
command_id = JSON.load(event.data)['command_id']
|
61
|
+
@messages[command_id] = event.data
|
62
|
+
end
|
59
63
|
end
|
60
64
|
|
61
65
|
def write(data)
|
@@ -64,25 +68,32 @@ module Capybara::Poltergeist
|
|
64
68
|
|
65
69
|
# Block until the next message is available from the Web Socket.
|
66
70
|
# Raises Errno::EWOULDBLOCK if timeout is reached.
|
67
|
-
def receive
|
71
|
+
def receive(cmd_id)
|
68
72
|
start = Time.now
|
69
73
|
|
70
|
-
until @messages.
|
74
|
+
until @messages.has_key?(cmd_id)
|
71
75
|
raise Errno::EWOULDBLOCK if (Time.now - start) >= timeout
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
+
if @receive_mutex.try_lock
|
77
|
+
begin
|
78
|
+
IO.select([socket], [], [], timeout) or raise Errno::EWOULDBLOCK
|
79
|
+
data = socket.recv(RECV_SIZE)
|
80
|
+
break if data.empty?
|
81
|
+
driver.parse(data)
|
82
|
+
ensure
|
83
|
+
@receive_mutex.unlock
|
84
|
+
end
|
85
|
+
else
|
86
|
+
sleep(0.05)
|
87
|
+
end
|
76
88
|
end
|
77
|
-
|
78
|
-
@messages.shift
|
89
|
+
@messages.delete(cmd_id)
|
79
90
|
end
|
80
91
|
|
81
92
|
# Send a message and block until there is a response
|
82
|
-
def send(message)
|
93
|
+
def send(cmd_id, message)
|
83
94
|
accept unless connected?
|
84
95
|
driver.text(message)
|
85
|
-
receive
|
96
|
+
receive(cmd_id)
|
86
97
|
rescue Errno::EWOULDBLOCK
|
87
98
|
raise TimeoutError.new(message)
|
88
99
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: poltergeist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Leighton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version:
|
89
|
+
version: 3.3.0
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version:
|
96
|
+
version: 3.3.0
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: sinatra
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -156,28 +156,28 @@ dependencies:
|
|
156
156
|
requirements:
|
157
157
|
- - "~>"
|
158
158
|
- !ruby/object:Gem::Version
|
159
|
-
version: 2.2
|
159
|
+
version: '2.2'
|
160
160
|
type: :development
|
161
161
|
prerelease: false
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
163
163
|
requirements:
|
164
164
|
- - "~>"
|
165
165
|
- !ruby/object:Gem::Version
|
166
|
-
version: 2.2
|
166
|
+
version: '2.2'
|
167
167
|
- !ruby/object:Gem::Dependency
|
168
168
|
name: guard-coffeescript
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version:
|
173
|
+
version: 2.0.0
|
174
174
|
type: :development
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version:
|
180
|
+
version: 2.0.0
|
181
181
|
description: Poltergeist is a driver for Capybara that allows you to run your tests
|
182
182
|
on a headless WebKit browser, provided by PhantomJS.
|
183
183
|
email:
|
@@ -193,8 +193,10 @@ files:
|
|
193
193
|
- lib/capybara/poltergeist/client.rb
|
194
194
|
- lib/capybara/poltergeist/client/agent.coffee
|
195
195
|
- lib/capybara/poltergeist/client/browser.coffee
|
196
|
+
- lib/capybara/poltergeist/client/cmd.coffee
|
196
197
|
- lib/capybara/poltergeist/client/compiled/agent.js
|
197
198
|
- lib/capybara/poltergeist/client/compiled/browser.js
|
199
|
+
- lib/capybara/poltergeist/client/compiled/cmd.js
|
198
200
|
- lib/capybara/poltergeist/client/compiled/connection.js
|
199
201
|
- lib/capybara/poltergeist/client/compiled/main.js
|
200
202
|
- lib/capybara/poltergeist/client/compiled/node.js
|
@@ -203,6 +205,7 @@ files:
|
|
203
205
|
- lib/capybara/poltergeist/client/main.coffee
|
204
206
|
- lib/capybara/poltergeist/client/node.coffee
|
205
207
|
- lib/capybara/poltergeist/client/web_page.coffee
|
208
|
+
- lib/capybara/poltergeist/command.rb
|
206
209
|
- lib/capybara/poltergeist/cookie.rb
|
207
210
|
- lib/capybara/poltergeist/driver.rb
|
208
211
|
- lib/capybara/poltergeist/errors.rb
|