poltergeist-cj 1.5.2

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