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,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