poltergeist-cj 1.5.2

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 (31) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +434 -0
  3. data/lib/capybara/poltergeist.rb +27 -0
  4. data/lib/capybara/poltergeist/browser.rb +339 -0
  5. data/lib/capybara/poltergeist/client.rb +151 -0
  6. data/lib/capybara/poltergeist/client/agent.coffee +374 -0
  7. data/lib/capybara/poltergeist/client/browser.coffee +393 -0
  8. data/lib/capybara/poltergeist/client/compiled/agent.js +535 -0
  9. data/lib/capybara/poltergeist/client/compiled/browser.js +526 -0
  10. data/lib/capybara/poltergeist/client/compiled/connection.js +25 -0
  11. data/lib/capybara/poltergeist/client/compiled/main.js +204 -0
  12. data/lib/capybara/poltergeist/client/compiled/node.js +77 -0
  13. data/lib/capybara/poltergeist/client/compiled/web_page.js +421 -0
  14. data/lib/capybara/poltergeist/client/connection.coffee +11 -0
  15. data/lib/capybara/poltergeist/client/main.coffee +89 -0
  16. data/lib/capybara/poltergeist/client/node.coffee +57 -0
  17. data/lib/capybara/poltergeist/client/web_page.coffee +297 -0
  18. data/lib/capybara/poltergeist/cookie.rb +35 -0
  19. data/lib/capybara/poltergeist/driver.rb +278 -0
  20. data/lib/capybara/poltergeist/errors.rb +178 -0
  21. data/lib/capybara/poltergeist/inspector.rb +46 -0
  22. data/lib/capybara/poltergeist/json.rb +25 -0
  23. data/lib/capybara/poltergeist/network_traffic.rb +6 -0
  24. data/lib/capybara/poltergeist/network_traffic/request.rb +26 -0
  25. data/lib/capybara/poltergeist/network_traffic/response.rb +40 -0
  26. data/lib/capybara/poltergeist/node.rb +154 -0
  27. data/lib/capybara/poltergeist/server.rb +36 -0
  28. data/lib/capybara/poltergeist/utility.rb +9 -0
  29. data/lib/capybara/poltergeist/version.rb +5 -0
  30. data/lib/capybara/poltergeist/web_socket_server.rb +96 -0
  31. metadata +285 -0
@@ -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,278 @@
1
+ require 'uri'
2
+
3
+ module Capybara::Poltergeist
4
+ class Driver < Capybara::Driver::Base
5
+ DEFAULT_TIMEOUT = 30
6
+
7
+ attr_reader :app, :server, :client, :browser, :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
+ list += ["--remote-debugger-port=#{inspector.port}", "--remote-debugger-autorun=yes"] if inspector
53
+ list
54
+ end
55
+
56
+ def client_pid
57
+ client.pid
58
+ end
59
+
60
+ def timeout
61
+ server.timeout
62
+ end
63
+
64
+ def timeout=(sec)
65
+ server.timeout = sec
66
+ end
67
+
68
+ def restart
69
+ browser.restart
70
+ end
71
+
72
+ def quit
73
+ server.stop
74
+ client.stop
75
+ end
76
+
77
+ # logger should be an object that responds to puts, or nil
78
+ def logger
79
+ options[:logger] || (options[:debug] && STDERR)
80
+ end
81
+
82
+ # logger should be an object that behaves like IO or nil
83
+ def phantomjs_logger
84
+ options.fetch(:phantomjs_logger, nil)
85
+ end
86
+
87
+ def visit(url)
88
+ @started = true
89
+ browser.visit(url)
90
+ end
91
+
92
+ def current_url
93
+ browser.current_url
94
+ end
95
+
96
+ def status_code
97
+ browser.status_code
98
+ end
99
+
100
+ def html
101
+ browser.body
102
+ end
103
+ alias_method :body, :html
104
+
105
+ def source
106
+ browser.source.to_s
107
+ end
108
+
109
+ def title
110
+ browser.title
111
+ end
112
+
113
+ def find(method, selector)
114
+ browser.find(method, selector).map { |page_id, id| Capybara::Poltergeist::Node.new(self, page_id, id) }
115
+ end
116
+
117
+ def find_xpath(selector)
118
+ find :xpath, selector
119
+ end
120
+
121
+ def find_css(selector)
122
+ find :css, selector
123
+ end
124
+
125
+ def click(x, y)
126
+ browser.click_coordinates(x, y)
127
+ end
128
+
129
+ def evaluate_script(script)
130
+ browser.evaluate(script)
131
+ end
132
+
133
+ def execute_script(script)
134
+ browser.execute(script)
135
+ nil
136
+ end
137
+
138
+ def within_frame(name, &block)
139
+ browser.within_frame(name, &block)
140
+ end
141
+
142
+ def within_window(name, &block)
143
+ browser.within_window(name, &block)
144
+ end
145
+
146
+ def window_handles
147
+ browser.window_handles
148
+ end
149
+
150
+ def reset!
151
+ browser.reset
152
+ @started = false
153
+ end
154
+
155
+ def save_screenshot(path, options = {})
156
+ browser.render(path, options)
157
+ end
158
+ alias_method :render, :save_screenshot
159
+
160
+ def render_base64(format = :png, options = {})
161
+ browser.render_base64(format, options)
162
+ end
163
+
164
+ def paper_size=(size = {})
165
+ browser.set_paper_size(size)
166
+ end
167
+
168
+ def zoom_factor=(zoom_factor)
169
+ browser.set_zoom_factor(zoom_factor)
170
+ end
171
+
172
+ def resize(width, height)
173
+ browser.resize(width, height)
174
+ end
175
+ alias_method :resize_window, :resize
176
+
177
+ def scroll_to(left, top)
178
+ browser.scroll_to(left, top)
179
+ end
180
+
181
+ def network_traffic
182
+ browser.network_traffic
183
+ end
184
+
185
+ def clear_network_traffic
186
+ browser.clear_network_traffic
187
+ end
188
+
189
+ def headers
190
+ browser.get_headers
191
+ end
192
+
193
+ def headers=(headers)
194
+ browser.set_headers(headers)
195
+ end
196
+
197
+ def add_headers(headers)
198
+ browser.add_headers(headers)
199
+ end
200
+
201
+ def add_header(name, value, options = {})
202
+ permanent = options.fetch(:permanent, true)
203
+ browser.add_header({ name => value }, permanent)
204
+ end
205
+
206
+ def response_headers
207
+ browser.response_headers
208
+ end
209
+
210
+ def cookies
211
+ browser.cookies
212
+ end
213
+
214
+ def set_cookie(name, value, options = {})
215
+ options[:name] ||= name
216
+ options[:value] ||= value
217
+ options[:domain] ||= begin
218
+ if @started
219
+ URI.parse(browser.current_url).host
220
+ else
221
+ Capybara.app_host || "127.0.0.1"
222
+ end
223
+ end
224
+
225
+ browser.set_cookie(options)
226
+ end
227
+
228
+ def remove_cookie(name)
229
+ browser.remove_cookie(name)
230
+ end
231
+
232
+ def cookies_enabled=(flag)
233
+ browser.cookies_enabled = flag
234
+ end
235
+
236
+ # * PhantomJS with set settings doesn't send `Authorize` on POST request
237
+ # * With manually set header PhantomJS makes next request with
238
+ # `Authorization: Basic Og==` header when settings are empty and the
239
+ # response was `401 Unauthorized` (which means Base64.encode64(':')).
240
+ # Combining both methods to reach proper behavior.
241
+ def basic_authorize(user, password)
242
+ browser.set_http_auth(user, password)
243
+ credentials = ["#{user}:#{password}"].pack('m*').strip
244
+ add_header('Authorization', "Basic #{credentials}")
245
+ end
246
+
247
+ def debug
248
+ if @options[:inspector]
249
+ inspector.open
250
+ pause
251
+ else
252
+ raise Error, "To use the remote debugging, you have to launch the driver " \
253
+ "with `:inspector => true` configuration option"
254
+ end
255
+ end
256
+
257
+ def pause
258
+ STDERR.puts "Poltergeist execution paused. Press enter to continue."
259
+ STDIN.gets
260
+ end
261
+
262
+ def wait?
263
+ true
264
+ end
265
+
266
+ def invalid_element_errors
267
+ [Capybara::Poltergeist::ObsoleteNode, Capybara::Poltergeist::MouseEventFailed]
268
+ end
269
+
270
+ def go_back
271
+ browser.go_back
272
+ end
273
+
274
+ def go_forward
275
+ browser.go_forward
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,178 @@
1
+ module Capybara
2
+ module Poltergeist
3
+ class Error < StandardError
4
+ end
5
+
6
+ class ClientError < Error
7
+ attr_reader :response
8
+
9
+ def initialize(response)
10
+ @response = response
11
+ end
12
+ end
13
+
14
+ class JSErrorItem
15
+ attr_reader :message, :stack
16
+
17
+ def initialize(message, stack)
18
+ @message = message
19
+ @stack = stack
20
+ end
21
+
22
+ def to_s
23
+ [message, stack].join("\n")
24
+ end
25
+ end
26
+
27
+ class BrowserError < ClientError
28
+ def name
29
+ response['name']
30
+ end
31
+
32
+ def javascript_error
33
+ JSErrorItem.new(*response['args'])
34
+ end
35
+
36
+ def message
37
+ "There was an error inside the PhantomJS portion of Poltergeist. " \
38
+ "This is probably a bug, so please report it. " \
39
+ "\n\n#{javascript_error}"
40
+ end
41
+ end
42
+
43
+ class JavascriptError < ClientError
44
+ def javascript_errors
45
+ response['args'].first.map { |data| JSErrorItem.new(data['message'], data['stack']) }
46
+ end
47
+
48
+ def message
49
+ "One or more errors were raised in the Javascript code on the page. " \
50
+ "If you don't care about these errors, you can ignore them by " \
51
+ "setting js_errors: false in your Poltergeist configuration (see " \
52
+ "documentation for details)." \
53
+ "\n\n#{javascript_errors.map(&:to_s).join("\n")}"
54
+ end
55
+ end
56
+
57
+ class StatusFailError < ClientError
58
+ def message
59
+ "Request failed to reach server, check DNS and/or server status"
60
+ end
61
+ end
62
+
63
+ class FrameNotFound < ClientError
64
+ def name
65
+ response['args'].first
66
+ end
67
+
68
+ def message
69
+ "The frame '#{name}' was not found."
70
+ end
71
+ end
72
+
73
+ class InvalidSelector < ClientError
74
+ def method
75
+ response['args'][0]
76
+ end
77
+
78
+ def selector
79
+ response['args'][1]
80
+ end
81
+
82
+ def message
83
+ "The browser raised a syntax error while trying to evaluate " \
84
+ "#{method} selector #{selector.inspect}"
85
+ end
86
+ end
87
+
88
+ class NodeError < ClientError
89
+ attr_reader :node
90
+
91
+ def initialize(node, response)
92
+ @node = node
93
+ super(response)
94
+ end
95
+ end
96
+
97
+ class ObsoleteNode < NodeError
98
+ def message
99
+ "The element you are trying to interact with is either not part of the DOM, or is " \
100
+ "not currently visible on the page (perhaps display: none is set). " \
101
+ "It's possible the element has been replaced by another element and you meant to interact with " \
102
+ "the new element. If so you need to do a new 'find' in order to get a reference to the " \
103
+ "new element."
104
+ end
105
+ end
106
+
107
+ class MouseEventFailed < NodeError
108
+ def name
109
+ response['args'][0]
110
+ end
111
+
112
+ def selector
113
+ response['args'][1]
114
+ end
115
+
116
+ def position
117
+ [response['args'][2]['x'], response['args'][2]['y']]
118
+ end
119
+
120
+ def message
121
+ "Firing a #{name} at co-ordinates [#{position.join(', ')}] failed. Poltergeist detected " \
122
+ "another element with CSS selector '#{selector}' at this position. " \
123
+ "It may be overlapping the element you are trying to interact with. " \
124
+ "If you don't care about overlapping elements, try using node.trigger('#{name}')."
125
+ end
126
+ end
127
+
128
+ class TimeoutError < Error
129
+ def initialize(message)
130
+ @message = message
131
+ end
132
+
133
+ def message
134
+ "Timed out waiting for response to #{@message}. It's possible that this happened " \
135
+ "because something took a very long time (for example a page load was slow). " \
136
+ "If so, setting the Poltergeist :timeout option to a higher value will help " \
137
+ "(see the docs for details). If increasing the timeout does not help, this is " \
138
+ "probably a bug in Poltergeist - please report it to the issue tracker."
139
+ end
140
+ end
141
+
142
+ class DeadClient < Error
143
+ def initialize(message)
144
+ @message = message
145
+ end
146
+
147
+ def message
148
+ "PhantomJS client died while processing #{@message}"
149
+ end
150
+ end
151
+
152
+ class PhantomJSTooOld < Error
153
+ def self.===(other)
154
+ if Cliver::Dependency::VersionMismatch === other
155
+ warn "#{name} exception has been deprecated in favor of using the " +
156
+ "cliver gem for command-line dependency detection. Please " +
157
+ "handle Cliver::Dependency::VersionMismatch instead."
158
+ true
159
+ else
160
+ super
161
+ end
162
+ end
163
+ end
164
+
165
+ class PhantomJSFailed < Error
166
+ def self.===(other)
167
+ if Cliver::Dependency::NotMet === other
168
+ warn "#{name} exception has been deprecated in favor of using the " +
169
+ "cliver gem for command-line dependency detection. Please " +
170
+ "handle Cliver::Dependency::NotMet instead."
171
+ true
172
+ else
173
+ super
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end