poltergeistFork 0.0.1

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