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,183 @@
1
+ module Capybara
2
+ module Poltergeist
3
+ class Error < StandardError; end
4
+ class NoSuchWindowError < Error; 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 error_parameters
33
+ response['args'].join("\n")
34
+ end
35
+
36
+ def message
37
+ "There was an error inside the PhantomJS portion of Poltergeist. " \
38
+ "If this is the error returned, and not the cause of a more detailed error response, " \
39
+ "this is probably a bug, so please report it. " \
40
+ "\n\n#{name}: #{error_parameters}"
41
+ end
42
+ end
43
+
44
+ class JavascriptError < ClientError
45
+ def javascript_errors
46
+ response['args'].first.map { |data| JSErrorItem.new(data['message'], data['stack']) }
47
+ end
48
+
49
+ def message
50
+ "One or more errors were raised in the Javascript code on the page. " \
51
+ "If you don't care about these errors, you can ignore them by " \
52
+ "setting js_errors: false in your Poltergeist configuration (see " \
53
+ "documentation for details)." \
54
+ "\n\n#{javascript_errors.map(&:to_s).join("\n")}"
55
+ end
56
+ end
57
+
58
+ class StatusFailError < ClientError
59
+ def url
60
+ response['args'].first
61
+ end
62
+
63
+ def message
64
+ "Request to '#{url}' failed to reach server, check DNS and/or server status"
65
+ end
66
+ end
67
+
68
+ class FrameNotFound < ClientError
69
+ def name
70
+ response['args'].first
71
+ end
72
+
73
+ def message
74
+ "The frame '#{name}' was not found."
75
+ end
76
+ end
77
+
78
+ class InvalidSelector < ClientError
79
+ def method
80
+ response['args'][0]
81
+ end
82
+
83
+ def selector
84
+ response['args'][1]
85
+ end
86
+
87
+ def message
88
+ "The browser raised a syntax error while trying to evaluate " \
89
+ "#{method} selector #{selector.inspect}"
90
+ end
91
+ end
92
+
93
+ class NodeError < ClientError
94
+ attr_reader :node
95
+
96
+ def initialize(node, response)
97
+ @node = node
98
+ super(response)
99
+ end
100
+ end
101
+
102
+ class ObsoleteNode < NodeError
103
+ def message
104
+ "The element you are trying to interact with is either not part of the DOM, or is " \
105
+ "not currently visible on the page (perhaps display: none is set). " \
106
+ "It's possible the element has been replaced by another element and you meant to interact with " \
107
+ "the new element. If so you need to do a new 'find' in order to get a reference to the " \
108
+ "new element."
109
+ end
110
+ end
111
+
112
+ class MouseEventFailed < NodeError
113
+ def name
114
+ response['args'][0]
115
+ end
116
+
117
+ def selector
118
+ response['args'][1]
119
+ end
120
+
121
+ def position
122
+ [response['args'][2]['x'], response['args'][2]['y']]
123
+ end
124
+
125
+ def message
126
+ "Firing a #{name} at co-ordinates [#{position.join(', ')}] failed. Poltergeist detected " \
127
+ "another element with CSS selector '#{selector}' at this position. " \
128
+ "It may be overlapping the element you are trying to interact with. " \
129
+ "If you don't care about overlapping elements, try using node.trigger('#{name}')."
130
+ end
131
+ end
132
+
133
+ class TimeoutError < Error
134
+ def initialize(message)
135
+ @message = message
136
+ end
137
+
138
+ def message
139
+ "Timed out waiting for response to #{@message}. It's possible that this happened " \
140
+ "because something took a very long time (for example a page load was slow). " \
141
+ "If so, setting the Poltergeist :timeout option to a higher value will help " \
142
+ "(see the docs for details). If increasing the timeout does not help, this is " \
143
+ "probably a bug in Poltergeist - please report it to the issue tracker."
144
+ end
145
+ end
146
+
147
+ class DeadClient < Error
148
+ def initialize(message)
149
+ @message = message
150
+ end
151
+
152
+ def message
153
+ "PhantomJS client died while processing #{@message}"
154
+ end
155
+ end
156
+
157
+ class PhantomJSTooOld < Error
158
+ def self.===(other)
159
+ if Cliver::Dependency::VersionMismatch === other
160
+ warn "#{name} exception has been deprecated in favor of using the " +
161
+ "cliver gem for command-line dependency detection. Please " +
162
+ "handle Cliver::Dependency::VersionMismatch instead."
163
+ true
164
+ else
165
+ super
166
+ end
167
+ end
168
+ end
169
+
170
+ class PhantomJSFailed < Error
171
+ def self.===(other)
172
+ if Cliver::Dependency::NotMet === other
173
+ warn "#{name} exception has been deprecated in favor of using the " +
174
+ "cliver gem for command-line dependency detection. Please " +
175
+ "handle Cliver::Dependency::NotMet instead."
176
+ true
177
+ else
178
+ super
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,46 @@
1
+ module Capybara::Poltergeist
2
+ class Inspector
3
+ BROWSERS = %w(chromium chromium-browser google-chrome open)
4
+ DEFAULT_PORT = 9664
5
+
6
+ def self.detect_browser
7
+ @browser ||= BROWSERS.find { |name| browser_binary_exists?(name) }
8
+ end
9
+
10
+ attr_reader :port
11
+
12
+ def initialize(browser = nil, port = DEFAULT_PORT)
13
+ @browser = browser.respond_to?(:to_str) ? browser : nil
14
+ @port = port
15
+ end
16
+
17
+ def browser
18
+ @browser ||= self.class.detect_browser
19
+ end
20
+
21
+ def url(scheme)
22
+ "#{scheme}://localhost:#{port}/"
23
+ end
24
+
25
+ def open(scheme)
26
+ if browser
27
+ Process.spawn(browser, url(scheme))
28
+ else
29
+ raise Error, "Could not find a browser executable to open #{url(scheme)}. " \
30
+ "You can specify one manually using e.g. `:inspector => 'chromium'` " \
31
+ "as a configuration option for Poltergeist."
32
+ end
33
+ end
34
+
35
+ def self.browser_binary_exists?(browser)
36
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
37
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
38
+ exts.each { |ext|
39
+ exe = "#{path}#{File::SEPARATOR}#{browser}#{ext}"
40
+ return exe if File.executable? exe
41
+ }
42
+ end
43
+ return nil
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,25 @@
1
+ module Capybara::Poltergeist
2
+ module JSON
3
+ def self.load(message)
4
+ if dumpy_multi_json?
5
+ MultiJson.load(message)
6
+ else
7
+ MultiJson.decode(message)
8
+ end
9
+ end
10
+
11
+ def self.dump(message)
12
+ if dumpy_multi_json?
13
+ MultiJson.dump(message)
14
+ else
15
+ MultiJson.encode(message)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def self.dumpy_multi_json?
22
+ MultiJson.respond_to?(:dump) && MultiJson.respond_to?(:load)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module Capybara::Poltergeist
2
+ module NetworkTraffic
3
+ require 'capybara/poltergeist/network_traffic/request'
4
+ require 'capybara/poltergeist/network_traffic/response'
5
+ require 'capybara/poltergeist/network_traffic/error'
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module Capybara::Poltergeist::NetworkTraffic
2
+ class Error
3
+ def initialize(data)
4
+ @data = data
5
+ end
6
+
7
+ def url
8
+ @data['url']
9
+ end
10
+
11
+ def code
12
+ @data['errorCode']
13
+ end
14
+
15
+ def description
16
+ @data['errorString']
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module Capybara::Poltergeist::NetworkTraffic
2
+ class Request
3
+ attr_reader :response_parts, :error
4
+
5
+ def initialize(data, response_parts = [], error = nil)
6
+ @data = data
7
+ @response_parts = response_parts
8
+ @error = error
9
+ end
10
+
11
+ def url
12
+ @data['url']
13
+ end
14
+
15
+ def method
16
+ @data['method']
17
+ end
18
+
19
+ def headers
20
+ @data['headers']
21
+ end
22
+
23
+ def time
24
+ @data['time'] && Time.parse(@data['time'])
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ module Capybara::Poltergeist::NetworkTraffic
2
+ class Response
3
+ def initialize(data)
4
+ @data = data
5
+ end
6
+
7
+ def url
8
+ @data['url']
9
+ end
10
+
11
+ def status
12
+ @data['status']
13
+ end
14
+
15
+ def status_text
16
+ @data['statusText']
17
+ end
18
+
19
+ def headers
20
+ @data['headers']
21
+ end
22
+
23
+ def redirect_url
24
+ @data['redirectURL']
25
+ end
26
+
27
+ def body_size
28
+ @data['bodySize']
29
+ end
30
+
31
+ def content_type
32
+ @data['contentType']
33
+ end
34
+
35
+ def time
36
+ @data['time'] && Time.parse(@data['time'])
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,177 @@
1
+ module Capybara::Poltergeist
2
+ class Node < Capybara::Driver::Node
3
+ attr_reader :page_id, :id
4
+
5
+ def initialize(driver, page_id, id)
6
+ super(driver, self)
7
+
8
+ @page_id = page_id
9
+ @id = id
10
+ end
11
+
12
+ def browser
13
+ driver.browser
14
+ end
15
+
16
+ def command(name, *args)
17
+ browser.send(name, page_id, id, *args)
18
+ rescue BrowserError => error
19
+ case error.name
20
+ when 'Poltergeist.ObsoleteNode'
21
+ raise ObsoleteNode.new(self, error.response)
22
+ when 'Poltergeist.MouseEventFailed'
23
+ raise MouseEventFailed.new(self, error.response)
24
+ else
25
+ raise
26
+ end
27
+ end
28
+
29
+ def parents
30
+ command(:parents).map { |parent_id| self.class.new(driver, page_id, parent_id) }
31
+ end
32
+
33
+ def find(method, selector)
34
+ command(:find_within, method, selector).map { |id| self.class.new(driver, page_id, id) }
35
+ end
36
+
37
+ def find_xpath(selector)
38
+ find :xpath, selector
39
+ end
40
+
41
+ def find_css(selector)
42
+ find :css, selector
43
+ end
44
+
45
+ def all_text
46
+ filter_text command(:all_text)
47
+ end
48
+
49
+ def visible_text
50
+ filter_text command(:visible_text)
51
+ end
52
+
53
+ def property(name)
54
+ command :property, name
55
+ end
56
+
57
+ def [](name)
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
70
+ end
71
+
72
+ def attributes
73
+ command :attributes
74
+ end
75
+
76
+ def value
77
+ command :value
78
+ end
79
+
80
+ def set(value)
81
+ if tag_name == 'input'
82
+ case self[:type]
83
+ when 'radio'
84
+ click
85
+ when 'checkbox'
86
+ click if value != checked?
87
+ when 'file'
88
+ files = value.respond_to?(:to_ary) ? value.to_ary.map(&:to_s) : value.to_s
89
+ command :select_file, files
90
+ else
91
+ command :set, value.to_s
92
+ end
93
+ elsif tag_name == 'textarea'
94
+ command :set, value.to_s
95
+ elsif self[:contenteditable] == 'true'
96
+ command :delete_text
97
+ send_keys(value.to_s)
98
+ end
99
+ end
100
+
101
+ def select_option
102
+ command :select, true
103
+ end
104
+
105
+ def unselect_option
106
+ command(:select, false) or
107
+ raise(Capybara::UnselectNotAllowed, "Cannot unselect option from single select box.")
108
+ end
109
+
110
+ def tag_name
111
+ @tag_name ||= command(:tag_name)
112
+ end
113
+
114
+ def visible?
115
+ command :visible?
116
+ end
117
+
118
+ def checked?
119
+ self[:checked]
120
+ end
121
+
122
+ def selected?
123
+ self[:selected]
124
+ end
125
+
126
+ def disabled?
127
+ command :disabled?
128
+ end
129
+
130
+ def click
131
+ command :click
132
+ end
133
+
134
+ def right_click
135
+ command :right_click
136
+ end
137
+
138
+ def double_click
139
+ command :double_click
140
+ end
141
+
142
+ def hover
143
+ command :hover
144
+ end
145
+
146
+ def drag_to(other)
147
+ command :drag, other.id
148
+ end
149
+
150
+ def drag_by(x, y)
151
+ command :drag_by, x, y
152
+ end
153
+
154
+ def trigger(event)
155
+ command :trigger, event
156
+ end
157
+
158
+ def ==(other)
159
+ command :equals, other.id
160
+ end
161
+
162
+ def send_keys(*keys)
163
+ command :send_keys, keys
164
+ end
165
+ alias_method :send_key, :send_keys
166
+
167
+ def path
168
+ command :path
169
+ end
170
+
171
+ private
172
+
173
+ def filter_text(text)
174
+ Capybara::Helpers.normalize_whitespace(text.to_s)
175
+ end
176
+ end
177
+ end