poltergeistFork 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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +425 -0
  4. data/lib/capybara/poltergeist/browser.rb +426 -0
  5. data/lib/capybara/poltergeist/client.rb +151 -0
  6. data/lib/capybara/poltergeist/client/agent.coffee +423 -0
  7. data/lib/capybara/poltergeist/client/browser.coffee +497 -0
  8. data/lib/capybara/poltergeist/client/cmd.coffee +17 -0
  9. data/lib/capybara/poltergeist/client/compiled/agent.js +587 -0
  10. data/lib/capybara/poltergeist/client/compiled/browser.js +687 -0
  11. data/lib/capybara/poltergeist/client/compiled/cmd.js +31 -0
  12. data/lib/capybara/poltergeist/client/compiled/connection.js +25 -0
  13. data/lib/capybara/poltergeist/client/compiled/main.js +228 -0
  14. data/lib/capybara/poltergeist/client/compiled/node.js +88 -0
  15. data/lib/capybara/poltergeist/client/compiled/web_page.js +539 -0
  16. data/lib/capybara/poltergeist/client/connection.coffee +11 -0
  17. data/lib/capybara/poltergeist/client/main.coffee +99 -0
  18. data/lib/capybara/poltergeist/client/node.coffee +70 -0
  19. data/lib/capybara/poltergeist/client/pre/agent.js +587 -0
  20. data/lib/capybara/poltergeist/client/pre/browser.js +688 -0
  21. data/lib/capybara/poltergeist/client/pre/cmd.js +31 -0
  22. data/lib/capybara/poltergeist/client/pre/connection.js +25 -0
  23. data/lib/capybara/poltergeist/client/pre/main.js +228 -0
  24. data/lib/capybara/poltergeist/client/pre/node.js +88 -0
  25. data/lib/capybara/poltergeist/client/pre/web_page.js +540 -0
  26. data/lib/capybara/poltergeist/client/web_page.coffee +372 -0
  27. data/lib/capybara/poltergeist/command.rb +17 -0
  28. data/lib/capybara/poltergeist/cookie.rb +35 -0
  29. data/lib/capybara/poltergeist/driver.rb +394 -0
  30. data/lib/capybara/poltergeist/errors.rb +183 -0
  31. data/lib/capybara/poltergeist/inspector.rb +46 -0
  32. data/lib/capybara/poltergeist/json.rb +25 -0
  33. data/lib/capybara/poltergeist/network_traffic.rb +7 -0
  34. data/lib/capybara/poltergeist/network_traffic/error.rb +19 -0
  35. data/lib/capybara/poltergeist/network_traffic/request.rb +27 -0
  36. data/lib/capybara/poltergeist/network_traffic/response.rb +40 -0
  37. data/lib/capybara/poltergeist/node.rb +177 -0
  38. data/lib/capybara/poltergeist/server.rb +36 -0
  39. data/lib/capybara/poltergeist/utility.rb +9 -0
  40. data/lib/capybara/poltergeist/version.rb +5 -0
  41. data/lib/capybara/poltergeist/web_socket_server.rb +107 -0
  42. data/lib/capybara/poltergeistFork.rb +27 -0
  43. metadata +268 -0
@@ -0,0 +1,372 @@
1
+ class Poltergeist.WebPage
2
+ @CALLBACKS = ['onConsoleMessage','onError',
3
+ 'onLoadFinished', 'onInitialized', 'onLoadStarted',
4
+ 'onResourceRequested', 'onResourceReceived', 'onResourceError',
5
+ 'onNavigationRequested', 'onUrlChanged', 'onPageCreated',
6
+ 'onClosing']
7
+
8
+ @DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render',
9
+ 'renderBase64', 'goBack', 'goForward']
10
+
11
+ @COMMANDS = ['currentUrl', 'find', 'nodeCall', 'documentSize',
12
+ 'beforeUpload', 'afterUpload', 'clearLocalStorage']
13
+
14
+ @EXTENSIONS = []
15
+
16
+ constructor: (@_native) ->
17
+ @_native or= require('webpage').create()
18
+
19
+ @id = 0
20
+ @source = null
21
+ @closed = false
22
+ @state = 'default'
23
+ @urlBlacklist = []
24
+ @frames = []
25
+ @errors = []
26
+ @_networkTraffic = {}
27
+ @_tempHeaders = {}
28
+ @_blockedUrls = []
29
+
30
+ for callback in WebPage.CALLBACKS
31
+ this.bindCallback(callback)
32
+
33
+ for command in @COMMANDS
34
+ do (command) =>
35
+ this.prototype[command] =
36
+ (args...) -> this.runCommand(command, args)
37
+
38
+ for delegate in @DELEGATES
39
+ do (delegate) =>
40
+ this.prototype[delegate] =
41
+ -> @_native[delegate].apply(@_native, arguments)
42
+
43
+ onInitializedNative: ->
44
+ @id += 1
45
+ @source = null
46
+ @injectAgent()
47
+ this.removeTempHeaders()
48
+ this.setScrollPosition(left: 0, top: 0)
49
+
50
+ onClosingNative: ->
51
+ @handle = null
52
+ @closed = true
53
+
54
+ onConsoleMessageNative: (message) ->
55
+ if message == '__DOMContentLoaded'
56
+ @source = @_native.content
57
+ false
58
+ else
59
+ console.log(message)
60
+
61
+ onLoadStartedNative: ->
62
+ @state = 'loading'
63
+ @requestId = @lastRequestId
64
+
65
+ onLoadFinishedNative: (@status) ->
66
+ @state = 'default'
67
+ @source or= @_native.content
68
+
69
+ onErrorNative: (message, stack) ->
70
+ stackString = message
71
+
72
+ stack.forEach (frame) ->
73
+ stackString += "\n"
74
+ stackString += " at #{frame.file}:#{frame.line}"
75
+ stackString += " in #{frame.function}" if frame.function && frame.function != ''
76
+
77
+ @errors.push(message: message, stack: stackString)
78
+ return true
79
+
80
+ onResourceRequestedNative: (request, net) ->
81
+ abort = @urlBlacklist.some (blacklisted_url) ->
82
+ request.url.indexOf(blacklisted_url) != -1
83
+
84
+ if abort
85
+ @_blockedUrls.push request.url unless request.url in @_blockedUrls
86
+ net.abort()
87
+ else
88
+ @lastRequestId = request.id
89
+
90
+ if @normalizeURL(request.url) == @redirectURL
91
+ @redirectURL = null
92
+ @requestId = request.id
93
+
94
+ @_networkTraffic[request.id] = {
95
+ request: request,
96
+ responseParts: []
97
+ error: null
98
+ }
99
+ return true
100
+
101
+ onResourceReceivedNative: (response) ->
102
+ @_networkTraffic[response.id]?.responseParts.push(response)
103
+
104
+ if @requestId == response.id
105
+ if response.redirectURL
106
+ @redirectURL = @normalizeURL(response.redirectURL)
107
+ else
108
+ @statusCode = response.status
109
+ @_responseHeaders = response.headers
110
+ return true
111
+
112
+ onResourceErrorNative: (errorResponse) ->
113
+ @_networkTraffic[errorResponse.id]?.error = errorResponse
114
+ return true
115
+
116
+ injectAgent: ->
117
+ if this.native().evaluate(-> typeof __poltergeist) == "undefined"
118
+ this.native().injectJs "#{phantom.libraryPath}/agent.js"
119
+ for extension in WebPage.EXTENSIONS
120
+ this.native().injectJs extension
121
+ return true
122
+ return false
123
+
124
+ injectExtension: (file) ->
125
+ WebPage.EXTENSIONS.push file
126
+ this.native().injectJs file
127
+
128
+ native: ->
129
+ if @closed
130
+ throw new Poltergeist.NoSuchWindowError
131
+ else
132
+ @_native
133
+
134
+ windowName: ->
135
+ this.native().windowName
136
+
137
+ keyCode: (name) ->
138
+ this.native().event.key[name]
139
+
140
+ keyModifierCode: (names) ->
141
+ modifiers = this.native().event.modifier
142
+ names = names.split(',').map ((name) -> modifiers[name])
143
+ names[0] | names[1] # return codes for 1 or 2 modifiers
144
+
145
+ keyModifierKeys: (names) ->
146
+ names.split(',').map (name) =>
147
+ this.keyCode(name.charAt(0).toUpperCase() + name.substring(1))
148
+
149
+ waitState: (state, callback) ->
150
+ if @state == state
151
+ callback.call()
152
+ else
153
+ setTimeout (=> @waitState(state, callback)), 100
154
+
155
+ setHttpAuth: (user, password) ->
156
+ this.native().settings.userName = user
157
+ this.native().settings.password = password
158
+ return true
159
+
160
+ networkTraffic: ->
161
+ @_networkTraffic
162
+
163
+ clearNetworkTraffic: ->
164
+ @_networkTraffic = {}
165
+ return true
166
+
167
+ blockedUrls: ->
168
+ @_blockedUrls
169
+
170
+ clearBlockedUrls: ->
171
+ @_blockedUrls = []
172
+ return true
173
+
174
+ content: ->
175
+ this.native().frameContent
176
+
177
+ title: ->
178
+ this.native().frameTitle
179
+
180
+ frameUrl: (frameNameOrId) ->
181
+ query = (frameNameOrId) ->
182
+ document.querySelector("iframe[name='#{frameNameOrId}'], iframe[id='#{frameNameOrId}']")?.src
183
+ this.evaluate(query, frameNameOrId)
184
+
185
+ clearErrors: ->
186
+ @errors = []
187
+ return true
188
+
189
+ responseHeaders: ->
190
+ headers = {}
191
+ @_responseHeaders.forEach (item) ->
192
+ headers[item.name] = item.value
193
+ headers
194
+
195
+ cookies: ->
196
+ this.native().cookies
197
+
198
+ deleteCookie: (name) ->
199
+ this.native().deleteCookie(name)
200
+
201
+ viewportSize: ->
202
+ this.native().viewportSize
203
+
204
+ setViewportSize: (size) ->
205
+ this.native().viewportSize = size
206
+
207
+ setZoomFactor: (zoom_factor) ->
208
+ this.native().zoomFactor = zoom_factor
209
+
210
+ setPaperSize: (size) ->
211
+ this.native().paperSize = size
212
+
213
+ scrollPosition: ->
214
+ this.native().scrollPosition
215
+
216
+ setScrollPosition: (pos) ->
217
+ this.native().scrollPosition = pos
218
+
219
+ clipRect: ->
220
+ this.native().clipRect
221
+
222
+ setClipRect: (rect) ->
223
+ this.native().clipRect = rect
224
+
225
+ elementBounds: (selector) ->
226
+ this.native().evaluate(
227
+ (selector) ->
228
+ document.querySelector(selector).getBoundingClientRect()
229
+ , selector
230
+ )
231
+
232
+ setUserAgent: (userAgent) ->
233
+ this.native().settings.userAgent = userAgent
234
+
235
+ getCustomHeaders: ->
236
+ this.native().customHeaders
237
+
238
+ setCustomHeaders: (headers) ->
239
+ this.native().customHeaders = headers
240
+
241
+ addTempHeader: (header) ->
242
+ for name, value of header
243
+ @_tempHeaders[name] = value
244
+ @_tempHeaders
245
+
246
+ removeTempHeaders: ->
247
+ allHeaders = this.getCustomHeaders()
248
+ for name, value of @_tempHeaders
249
+ delete allHeaders[name]
250
+ this.setCustomHeaders(allHeaders)
251
+
252
+ pushFrame: (name) ->
253
+ if this.native().switchToFrame(name)
254
+ @frames.push(name)
255
+ return true
256
+ else
257
+ frame_no = this.native().evaluate(
258
+ (frame_name) ->
259
+ frames = document.querySelectorAll("iframe, frame")
260
+ (idx for f, idx in frames when f?['name'] == frame_name or f?['id'] == frame_name)[0]
261
+ , name)
262
+ if frame_no? and this.native().switchToFrame(frame_no)
263
+ @frames.push(name)
264
+ return true
265
+ else
266
+ return false
267
+
268
+ popFrame: ->
269
+ @frames.pop()
270
+ this.native().switchToParentFrame()
271
+
272
+ dimensions: ->
273
+ scroll = this.scrollPosition()
274
+ viewport = this.viewportSize()
275
+
276
+ top: scroll.top, bottom: scroll.top + viewport.height,
277
+ left: scroll.left, right: scroll.left + viewport.width,
278
+ viewport: viewport
279
+ document: this.documentSize()
280
+
281
+ # A work around for http://code.google.com/p/phantomjs/issues/detail?id=277
282
+ validatedDimensions: ->
283
+ dimensions = this.dimensions()
284
+ document = dimensions.document
285
+
286
+ if dimensions.right > document.width
287
+ dimensions.left = Math.max(0, dimensions.left - (dimensions.right - document.width))
288
+ dimensions.right = document.width
289
+
290
+ if dimensions.bottom > document.height
291
+ dimensions.top = Math.max(0, dimensions.top - (dimensions.bottom - document.height))
292
+ dimensions.bottom = document.height
293
+
294
+ this.setScrollPosition(left: dimensions.left, top: dimensions.top)
295
+
296
+ dimensions
297
+
298
+ get: (id) ->
299
+ new Poltergeist.Node(this, id)
300
+
301
+ # Before each mouse event we make sure that the mouse is moved to where the
302
+ # event will take place. This deals with e.g. :hover changes.
303
+ mouseEvent: (name, x, y, button = 'left') ->
304
+ this.sendEvent('mousemove', x, y)
305
+ this.sendEvent(name, x, y, button)
306
+
307
+ evaluate: (fn, args...) ->
308
+ this.injectAgent()
309
+ JSON.parse this.sanitize(this.native().evaluate("function() { return PoltergeistAgent.stringify(#{this.stringifyCall(fn, args)}) }"))
310
+
311
+ sanitize: (potential_string) ->
312
+ if typeof(potential_string) == "string"
313
+ # JSON doesn't like \r or \n in strings unless escaped
314
+ potential_string.replace("\n","\\n").replace("\r","\\r")
315
+ else
316
+ potential_string
317
+
318
+ execute: (fn, args...) ->
319
+ this.native().evaluate("function() { #{this.stringifyCall(fn, args)} }")
320
+
321
+ stringifyCall: (fn, args) ->
322
+ if args.length == 0
323
+ "(#{fn.toString()})()"
324
+ else
325
+ # The JSON.stringify happens twice because the second time we are essentially
326
+ # escaping the string.
327
+ "(#{fn.toString()}).apply(this, PoltergeistAgent.JSON.parse(#{JSON.stringify(JSON.stringify(args))}))"
328
+
329
+ # For some reason phantomjs seems to have trouble with doing 'fat arrow' binding here,
330
+ # hence the 'that' closure.
331
+ bindCallback: (name) ->
332
+ that = this
333
+ this.native()[name] = ->
334
+ if that[name + 'Native']? # For internal callbacks
335
+ result = that[name + 'Native'].apply(that, arguments)
336
+
337
+ if result != false && that[name]? # For externally set callbacks
338
+ that[name].apply(that, arguments)
339
+ return true
340
+
341
+ # Any error raised here or inside the evaluate will get reported to
342
+ # phantom.onError. If result is null, that means there was an error
343
+ # inside the agent.
344
+ runCommand: (name, args) ->
345
+ result = this.evaluate(
346
+ (name, args) -> __poltergeist.externalCall(name, args),
347
+ name, args
348
+ )
349
+
350
+ if result != null
351
+ if result.error?
352
+ switch result.error.message
353
+ when 'PoltergeistAgent.ObsoleteNode'
354
+ throw new Poltergeist.ObsoleteNode
355
+ when 'PoltergeistAgent.InvalidSelector'
356
+ [method, selector] = args
357
+ throw new Poltergeist.InvalidSelector(method, selector)
358
+ else
359
+ throw new Poltergeist.BrowserError(result.error.message, result.error.stack)
360
+ else
361
+ result.value
362
+
363
+ canGoBack: ->
364
+ this.native().canGoBack
365
+
366
+ canGoForward: ->
367
+ this.native().canGoForward
368
+
369
+ normalizeURL: (url) ->
370
+ parser = document.createElement('a')
371
+ parser.href = url
372
+ 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
@@ -0,0 +1,35 @@
1
+ module Capybara::Poltergeist
2
+ class Cookie
3
+ def initialize(attributes)
4
+ @attributes = attributes
5
+ end
6
+
7
+ def name
8
+ @attributes['name']
9
+ end
10
+
11
+ def value
12
+ @attributes['value']
13
+ end
14
+
15
+ def domain
16
+ @attributes['domain']
17
+ end
18
+
19
+ def path
20
+ @attributes['path']
21
+ end
22
+
23
+ def secure?
24
+ @attributes['secure']
25
+ end
26
+
27
+ def httponly?
28
+ @attributes['httponly']
29
+ end
30
+
31
+ def expires
32
+ Time.at @attributes['expiry'] if @attributes['expiry']
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,394 @@
1
+ require 'uri'
2
+
3
+ module Capybara::Poltergeist
4
+ class Driver < Capybara::Driver::Base
5
+ DEFAULT_TIMEOUT = 30
6
+
7
+ attr_reader :app, :options
8
+
9
+ def initialize(app, options = {})
10
+ @app = app
11
+ @options = options
12
+ @browser = nil
13
+ @inspector = nil
14
+ @server = nil
15
+ @client = nil
16
+ @started = false
17
+ end
18
+
19
+ def needs_server?
20
+ true
21
+ end
22
+
23
+ def browser
24
+ @browser ||= begin
25
+ browser = Browser.new(server, client, logger)
26
+ browser.js_errors = options[:js_errors] if options.key?(:js_errors)
27
+ browser.extensions = options.fetch(:extensions, [])
28
+ browser.debug = true if options[:debug]
29
+ browser
30
+ end
31
+ end
32
+
33
+ def inspector
34
+ @inspector ||= options[:inspector] && Inspector.new(options[:inspector])
35
+ end
36
+
37
+ def server
38
+ @server ||= Server.new(options[:port], options.fetch(:timeout) { DEFAULT_TIMEOUT })
39
+ end
40
+
41
+ def client
42
+ @client ||= Client.start(server,
43
+ :path => options[:phantomjs],
44
+ :window_size => options[:window_size],
45
+ :phantomjs_options => phantomjs_options,
46
+ :phantomjs_logger => phantomjs_logger
47
+ )
48
+ end
49
+
50
+ def phantomjs_options
51
+ list = options[:phantomjs_options] || []
52
+
53
+ # PhantomJS defaults to only using SSLv3, which since POODLE (Oct 2014)
54
+ # many sites have dropped from their supported protocols (eg PayPal,
55
+ # Braintree).
56
+ list += ["--ssl-protocol=any"] unless list.grep(/ssl-protocol/).any?
57
+
58
+ list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector
59
+ list
60
+ end
61
+
62
+ def client_pid
63
+ client.pid
64
+ end
65
+
66
+ def timeout
67
+ server.timeout
68
+ end
69
+
70
+ def timeout=(sec)
71
+ server.timeout = sec
72
+ end
73
+
74
+ def restart
75
+ browser.restart
76
+ end
77
+
78
+ def quit
79
+ server.stop
80
+ client.stop
81
+ end
82
+
83
+ # logger should be an object that responds to puts, or nil
84
+ def logger
85
+ options[:logger] || (options[:debug] && STDERR)
86
+ end
87
+
88
+ # logger should be an object that behaves like IO or nil
89
+ def phantomjs_logger
90
+ options.fetch(:phantomjs_logger, nil)
91
+ end
92
+
93
+ def visit(url)
94
+ @started = true
95
+ browser.visit(url)
96
+ end
97
+
98
+ def current_url
99
+ browser.current_url
100
+ end
101
+
102
+ def status_code
103
+ browser.status_code
104
+ end
105
+
106
+ def html
107
+ browser.body
108
+ end
109
+ alias_method :body, :html
110
+
111
+ def source
112
+ browser.source.to_s
113
+ end
114
+
115
+ def title
116
+ browser.title
117
+ end
118
+
119
+ def find(method, selector)
120
+ browser.find(method, selector).map { |page_id, id| Capybara::Poltergeist::Node.new(self, page_id, id) }
121
+ end
122
+
123
+ def find_xpath(selector)
124
+ find :xpath, selector
125
+ end
126
+
127
+ def find_css(selector)
128
+ find :css, selector
129
+ end
130
+
131
+ def click(x, y)
132
+ browser.click_coordinates(x, y)
133
+ end
134
+
135
+ def evaluate_script(script)
136
+ browser.evaluate(script)
137
+ end
138
+
139
+ def execute_script(script)
140
+ browser.execute(script)
141
+ nil
142
+ end
143
+
144
+ def within_frame(name, &block)
145
+ browser.within_frame(name, &block)
146
+ end
147
+
148
+ def current_window_handle
149
+ browser.window_handle
150
+ end
151
+
152
+ def window_handles
153
+ browser.window_handles
154
+ end
155
+
156
+ def close_window(handle)
157
+ browser.close_window(handle)
158
+ end
159
+
160
+ def open_new_window
161
+ browser.open_new_window
162
+ end
163
+
164
+ def switch_to_window(handle)
165
+ browser.switch_to_window(handle)
166
+ end
167
+
168
+ def within_window(name, &block)
169
+ browser.within_window(name, &block)
170
+ end
171
+
172
+ def no_such_window_error
173
+ NoSuchWindowError
174
+ end
175
+
176
+ def reset!
177
+ browser.reset
178
+ @started = false
179
+ end
180
+
181
+ def save_screenshot(path, options = {})
182
+ browser.render(path, options)
183
+ end
184
+ alias_method :render, :save_screenshot
185
+
186
+ def render_base64(format = :png, options = {})
187
+ browser.render_base64(format, options)
188
+ end
189
+
190
+ def set_screen_size(s_width,s_height)
191
+ browser.set_screen_size(s_width, s_width)
192
+ end
193
+
194
+ def paper_size=(size = {})
195
+ browser.set_paper_size(size)
196
+ end
197
+
198
+ def zoom_factor=(zoom_factor)
199
+ browser.set_zoom_factor(zoom_factor)
200
+ end
201
+
202
+ def resize(width, height)
203
+ browser.resize(width, height)
204
+ end
205
+ alias_method :resize_window, :resize
206
+
207
+ def resize_window_to(handle, width, height)
208
+ within_window(handle) do
209
+ resize(width, height)
210
+ end
211
+ end
212
+
213
+ def window_size(handle)
214
+ within_window(handle) do
215
+ evaluate_script('[window.innerWidth, window.innerHeight]')
216
+ end
217
+ end
218
+
219
+ def scroll_to(left, top)
220
+ browser.scroll_to(left, top)
221
+ end
222
+
223
+ def network_traffic
224
+ browser.network_traffic
225
+ end
226
+
227
+ def clear_network_traffic
228
+ browser.clear_network_traffic
229
+ end
230
+
231
+ def headers
232
+ browser.get_headers
233
+ end
234
+
235
+ def headers=(headers)
236
+ browser.set_headers(headers)
237
+ end
238
+
239
+ def add_headers(headers)
240
+ browser.add_headers(headers)
241
+ end
242
+
243
+ def add_header(name, value, options = {})
244
+ permanent = options.fetch(:permanent, true)
245
+ browser.add_header({ name => value }, permanent)
246
+ end
247
+
248
+ def response_headers
249
+ browser.response_headers
250
+ end
251
+
252
+ def cookies
253
+ browser.cookies
254
+ end
255
+
256
+ def set_cookie(name, value, options = {})
257
+ options[:name] ||= name
258
+ options[:value] ||= value
259
+ options[:domain] ||= begin
260
+ if @started
261
+ URI.parse(browser.current_url).host
262
+ else
263
+ URI.parse(Capybara.app_host || '').host || "127.0.0.1"
264
+ end
265
+ end
266
+
267
+ browser.set_cookie(options)
268
+ end
269
+
270
+ def remove_cookie(name)
271
+ browser.remove_cookie(name)
272
+ end
273
+
274
+ def clear_cookies
275
+ browser.clear_cookies
276
+ end
277
+
278
+ def cookies_enabled=(flag)
279
+ browser.cookies_enabled = flag
280
+ end
281
+
282
+ # * PhantomJS with set settings doesn't send `Authorize` on POST request
283
+ # * With manually set header PhantomJS makes next request with
284
+ # `Authorization: Basic Og==` header when settings are empty and the
285
+ # response was `401 Unauthorized` (which means Base64.encode64(':')).
286
+ # Combining both methods to reach proper behavior.
287
+ def basic_authorize(user, password)
288
+ browser.set_http_auth(user, password)
289
+ credentials = ["#{user}:#{password}"].pack('m*').strip
290
+ add_header('Authorization', "Basic #{credentials}")
291
+ end
292
+
293
+ def debug
294
+ if @options[:inspector]
295
+ # Fall back to default scheme
296
+ scheme = URI.parse(browser.current_url).scheme rescue nil
297
+ scheme = 'http' if scheme != 'https'
298
+ inspector.open(scheme)
299
+ pause
300
+ else
301
+ raise Error, "To use the remote debugging, you have to launch the driver " \
302
+ "with `:inspector => true` configuration option"
303
+ end
304
+ end
305
+
306
+ def pause
307
+ # STDIN is not necessarily connected to a keyboard. It might even be closed.
308
+ # So we need a method other than keypress to continue.
309
+
310
+ # In jRuby - STDIN returns immediately from select
311
+ # see https://github.com/jruby/jruby/issues/1783
312
+ read, write = IO.pipe
313
+ Thread.new { IO.copy_stream(STDIN, write); write.close }
314
+
315
+ STDERR.puts "Poltergeist execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue."
316
+
317
+ signal = false
318
+ old_trap = trap('SIGCONT') { signal = true; STDERR.puts "\nSignal SIGCONT received" }
319
+ keyboard = IO.select([read], nil, nil, 1) until keyboard || signal # wait for data on STDIN or signal SIGCONT received
320
+
321
+ begin
322
+ input = read.read_nonblock(80) # clear out the read buffer
323
+ puts unless input && input =~ /\n\z/
324
+ rescue EOFError, IO::WaitReadable # Ignore problems reading from STDIN.
325
+ end unless signal
326
+
327
+ trap('SIGCONT', old_trap) # Restore the previuos signal handler, if there was one.
328
+
329
+ STDERR.puts 'Continuing'
330
+ end
331
+
332
+ def wait?
333
+ true
334
+ end
335
+
336
+ def invalid_element_errors
337
+ [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::MouseEventFailed]
338
+ end
339
+
340
+ def go_back
341
+ browser.go_back
342
+ end
343
+
344
+ def go_forward
345
+ browser.go_forward
346
+ end
347
+
348
+ def accept_modal(type, options = {})
349
+ case type
350
+ when :confirm
351
+ browser.accept_confirm
352
+ when :prompt
353
+ browser.accept_prompt options[:with]
354
+ end
355
+
356
+ yield if block_given?
357
+
358
+ find_modal(options)
359
+ end
360
+
361
+ def dismiss_modal(type, options = {})
362
+ case type
363
+ when :confirm
364
+ browser.dismiss_confirm
365
+ when :prompt
366
+ browser.dismiss_prompt
367
+ end
368
+
369
+ yield if block_given?
370
+ find_modal(options)
371
+ end
372
+
373
+ private
374
+
375
+ def find_modal(options)
376
+ start_time = Time.now
377
+ timeout_sec = options[:wait] || begin Capybara.default_max_wait_time rescue Capybara.default_wait_time end
378
+ expect_text = options[:text]
379
+ not_found_msg = 'Unable to find modal dialog'
380
+ not_found_msg += " with #{expect_text}" if expect_text
381
+
382
+ begin
383
+ modal_text = browser.modal_message
384
+ raise Capybara::ModalNotFound if modal_text.nil?
385
+ raise Capybara::ModalNotFound if (expect_text && (modal_text != expect_text))
386
+ rescue Capybara::ModalNotFound => e
387
+ raise e, not_found_msg if (Time.now - start_time) >= timeout_sec
388
+ sleep(0.05)
389
+ retry
390
+ end
391
+ modal_text
392
+ end
393
+ end
394
+ end