cuprite 0.6.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "uri"
4
-
5
4
  require "forwardable"
6
5
 
7
6
  module Capybara::Cuprite
8
7
  class Driver < Capybara::Driver::Base
8
+ DEFAULT_MAXIMIZE_SCREEN_SIZE = [1366, 768].freeze
9
+ EXTENSION = File.expand_path("javascripts/index.js", __dir__)
10
+
9
11
  extend Forwardable
10
12
 
11
13
  delegate %i(restart quit status_code timeout timeout=) => :browser
12
14
 
13
- attr_reader :app, :options
15
+ attr_reader :app, :options, :screen_size
14
16
 
15
17
  def initialize(app, options = {})
16
18
  @app = app
17
- @options = options
19
+ @options = options.dup
18
20
  @started = false
21
+
22
+ @options[:extensions] ||= []
23
+ @options[:extensions] << EXTENSION
24
+
25
+ @screen_size = @options.delete(:screen_size)
26
+ @screen_size ||= DEFAULT_MAXIMIZE_SCREEN_SIZE
27
+
28
+ @options[:save_path] = Capybara.save_path.to_s if Capybara.save_path
19
29
  end
20
30
 
21
31
  def needs_server?
@@ -23,7 +33,7 @@ module Capybara::Cuprite
23
33
  end
24
34
 
25
35
  def browser
26
- @browser ||= Browser.start(@options)
36
+ @browser ||= Browser.new(@options)
27
37
  end
28
38
 
29
39
  def visit(url)
@@ -64,20 +74,20 @@ module Capybara::Cuprite
64
74
  browser.frame_title
65
75
  end
66
76
 
67
- def find(method, selector)
68
- browser.find(method, selector).map { |target_id, node| Node.new(self, target_id, node) }
69
- end
70
-
71
77
  def find_xpath(selector)
72
- find :xpath, selector
78
+ find(:xpath, selector)
73
79
  end
74
80
 
75
81
  def find_css(selector)
76
- find :css, selector
82
+ find(:css, selector)
83
+ end
84
+
85
+ def find(method, selector)
86
+ browser.find(method, selector).map { |native| Node.new(self, native) }
77
87
  end
78
88
 
79
89
  def click(x, y)
80
- browser.click_coordinates(x, y)
90
+ browser.mouse.click(x: x, y: y)
81
91
  end
82
92
 
83
93
  def evaluate_script(script, *args)
@@ -96,7 +106,14 @@ module Capybara::Cuprite
96
106
  end
97
107
 
98
108
  def switch_to_frame(locator)
99
- browser.switch_to_frame(locator)
109
+ handle = case locator
110
+ when Capybara::Node::Element
111
+ locator.native.description["frameId"]
112
+ when :parent, :top
113
+ locator
114
+ end
115
+
116
+ browser.switch_to_frame(handle)
100
117
  end
101
118
 
102
119
  def current_window_handle
@@ -124,31 +141,47 @@ module Capybara::Cuprite
124
141
  end
125
142
 
126
143
  def no_such_window_error
127
- NoSuchWindowError
144
+ Ferrum::NoSuchWindowError
128
145
  end
129
146
 
130
147
  def reset!
131
- browser.reset
148
+ @zoom_factor = nil
149
+ @paper_size = nil
132
150
  browser.url_blacklist = @options[:url_blacklist]
133
151
  browser.url_whitelist = @options[:url_whitelist]
152
+ browser.reset
134
153
  @started = false
135
154
  end
136
155
 
137
156
  def save_screenshot(path, options = {})
138
- browser.render(path, options)
157
+ options[:scale] = @zoom_factor if @zoom_factor
158
+
159
+ if pdf?(path, options)
160
+ options[:paperWidth] = @paper_size[:width].to_f if @paper_size
161
+ options[:paperHeight] = @paper_size[:height].to_f if @paper_size
162
+ browser.pdf(path: path, **options)
163
+ else
164
+ browser.screenshot(path: path, **options)
165
+ end
139
166
  end
140
167
  alias_method :render, :save_screenshot
141
168
 
142
169
  def render_base64(format = :png, options = {})
143
- browser.render_base64(format, options)
170
+ if pdf?(nil, options)
171
+ options[:paperWidth] = @paper_size[:width].to_f if @paper_size
172
+ options[:paperHeight] = @paper_size[:height].to_f if @paper_size
173
+ browser.pdf(encoding: :base64, **options)
174
+ else
175
+ browser.screenshot(format: format, encoding: :base64, **options)
176
+ end
144
177
  end
145
178
 
146
- def paper_size=(size = {})
147
- browser.set_paper_size(size)
179
+ def zoom_factor=(value)
180
+ @zoom_factor = value.to_f
148
181
  end
149
182
 
150
- def zoom_factor=(zoom_factor)
151
- browser.set_zoom_factor(zoom_factor)
183
+ def paper_size=(value)
184
+ @paper_size = value
152
185
  end
153
186
 
154
187
  def resize(width, height)
@@ -179,15 +212,25 @@ module Capybara::Cuprite
179
212
  end
180
213
 
181
214
  def scroll_to(left, top)
182
- browser.scroll_to(left, top)
215
+ browser.mouse.scroll_to(left, top)
183
216
  end
184
217
 
185
218
  def network_traffic(type = nil)
186
- browser.network_traffic(type)
219
+ traffic = browser.network.traffic
220
+
221
+ case type.to_s
222
+ when "all"
223
+ traffic
224
+ when "blocked"
225
+ traffic.select(&:blocked?)
226
+ else
227
+ # when request isn't blocked
228
+ traffic.reject(&:blocked?)
229
+ end
187
230
  end
188
231
 
189
232
  def clear_network_traffic
190
- browser.clear_network_traffic
233
+ browser.network.clear(:traffic)
191
234
  end
192
235
 
193
236
  def set_proxy(ip, port, type = nil, user = nil, password = nil, bypass = nil)
@@ -195,31 +238,31 @@ module Capybara::Cuprite
195
238
  server = type ? "#{type}=#{ip}:#{port}" : "#{ip}:#{port}"
196
239
  @options[:browser_options].merge!("proxy-server" => server)
197
240
  @options[:browser_options].merge!("proxy-bypass-list" => bypass) if bypass
198
- browser.proxy_authorize(user, password)
241
+ browser.network.authorize(type: :proxy, user: user, password: password)
199
242
  end
200
243
 
201
244
  def headers
202
- browser.headers
245
+ browser.headers.get
203
246
  end
204
247
 
205
248
  def headers=(headers)
206
- browser.headers=(headers)
249
+ browser.headers.set(headers)
207
250
  end
208
251
 
209
252
  def add_headers(headers)
210
- browser.add_headers(headers)
253
+ browser.headers.add(headers)
211
254
  end
212
255
 
213
256
  def add_header(name, value, permanent: true)
214
- browser.add_header({ name => value }, permanent: permanent)
257
+ browser.headers.add({ name => value }, permanent: permanent)
215
258
  end
216
259
 
217
260
  def response_headers
218
- browser.response_headers
261
+ browser.network.response&.headers
219
262
  end
220
263
 
221
264
  def cookies
222
- browser.cookies
265
+ browser.cookies.all
223
266
  end
224
267
 
225
268
  def set_cookie(name, value, options = {})
@@ -227,31 +270,37 @@ module Capybara::Cuprite
227
270
  options[:name] ||= name
228
271
  options[:value] ||= value
229
272
  options[:domain] ||= default_domain
230
-
231
- expires = options.delete(:expires).to_i
232
- options[:expires] = expires if expires > 0
233
-
234
- browser.set_cookie(options)
273
+ browser.cookies.set(**options)
235
274
  end
236
275
 
237
276
  def remove_cookie(name, **options)
238
277
  options[:domain] = default_domain if options.empty?
239
- browser.remove_cookie(options.merge(name: name))
278
+ browser.cookies.remove(**options.merge(name: name))
240
279
  end
241
280
 
242
281
  def clear_cookies
243
- browser.clear_cookies
282
+ browser.cookies.clear
244
283
  end
245
284
 
246
285
  def clear_memory_cache
247
- browser.clear_memory_cache
286
+ browser.network.clear(:cache)
248
287
  end
249
288
 
250
289
  def basic_authorize(user, password)
251
- browser.authorize(user, password)
290
+ browser.network.authorize(user: user, password: password)
252
291
  end
253
292
  alias_method :authorize, :basic_authorize
254
293
 
294
+ def debug
295
+ if @options[:inspector]
296
+ Process.spawn(browser.process.path, "http://#{browser.process.host}:#{browser.process.port}")
297
+ pause
298
+ else
299
+ raise Error, "To use the remote debugging, you have to launch " \
300
+ "the driver with `inspector: true` configuration option"
301
+ end
302
+ end
303
+
255
304
  def pause
256
305
  # STDIN is not necessarily connected to a keyboard. It might even be closed.
257
306
  # So we need a method other than keypress to continue.
@@ -284,15 +333,17 @@ module Capybara::Cuprite
284
333
  end
285
334
 
286
335
  def invalid_element_errors
287
- [Capybara::Cuprite::ObsoleteNode, Capybara::Cuprite::MouseEventFailed]
336
+ [Capybara::Cuprite::ObsoleteNode,
337
+ Capybara::Cuprite::MouseEventFailed,
338
+ Ferrum::NoExecutionContextError]
288
339
  end
289
340
 
290
341
  def go_back
291
- browser.go_back
342
+ browser.back
292
343
  end
293
344
 
294
345
  def go_forward
295
- browser.go_forward
346
+ browser.forward
296
347
  end
297
348
 
298
349
  def refresh
@@ -336,11 +387,7 @@ module Capybara::Cuprite
336
387
  end
337
388
 
338
389
  def native_args(args)
339
- args.map { |arg| arg.is_a?(Capybara::Cuprite::Node) ? arg.native : arg }
340
- end
341
-
342
- def screen_size
343
- @options[:screen_size] || [1366, 768]
390
+ args.map { |arg| arg.is_a?(Capybara::Cuprite::Node) ? arg.node : arg }
344
391
  end
345
392
 
346
393
  def session_wait_time
@@ -368,11 +415,17 @@ module Capybara::Cuprite
368
415
  when Array
369
416
  arg.map { |e| unwrap_script_result(e) }
370
417
  when Hash
371
- return Capybara::Cuprite::Node.new(self, arg["target_id"], arg["node"]) if arg["target_id"]
372
418
  arg.each { |k, v| arg[k] = unwrap_script_result(v) }
419
+ when Ferrum::Node
420
+ Node.new(self, arg)
373
421
  else
374
422
  arg
375
423
  end
376
424
  end
425
+
426
+ def pdf?(path, options)
427
+ (path && File.extname(path).delete(".") == "pdf") ||
428
+ options[:format].to_s == "pdf"
429
+ end
377
430
  end
378
431
  end
@@ -3,7 +3,6 @@
3
3
  module Capybara
4
4
  module Cuprite
5
5
  class Error < StandardError; end
6
- class NoSuchWindowError < Error; end
7
6
 
8
7
  class ClientError < Error
9
8
  attr_reader :response
@@ -13,45 +12,6 @@ module Capybara
13
12
  end
14
13
  end
15
14
 
16
- class BrowserError < ClientError
17
- def code
18
- response["code"]
19
- end
20
-
21
- def data
22
- response["data"]
23
- end
24
-
25
- def message
26
- response["message"]
27
- end
28
- end
29
-
30
- class JavaScriptError < ClientError
31
- attr_reader :class_name, :message
32
-
33
- def initialize(response)
34
- super
35
- @class_name, @message = response.values_at("className", "description")
36
- end
37
- end
38
-
39
- class StatusFailError < ClientError
40
- def message
41
- "Request to #{response["url"]} failed to reach server, check DNS and/or server status"
42
- end
43
- end
44
-
45
- class FrameNotFound < ClientError
46
- def name
47
- response["args"].first
48
- end
49
-
50
- def message
51
- "The frame "#{name}" was not found."
52
- end
53
- end
54
-
55
15
  class InvalidSelector < ClientError
56
16
  def initialize(response, method, selector)
57
17
  super(response)
@@ -82,16 +42,14 @@ module Capybara
82
42
  end
83
43
  end
84
44
 
85
- class NodeError < ClientError
45
+ class ObsoleteNode < ClientError
86
46
  attr_reader :node
87
47
 
88
48
  def initialize(node, response)
89
49
  @node = node
90
50
  super(response)
91
51
  end
92
- end
93
52
 
94
- class ObsoleteNode < NodeError
95
53
  def message
96
54
  "The element you are trying to interact with is either not part of the DOM, or is " \
97
55
  "not currently visible on the page (perhaps display: none is set). " \
@@ -100,26 +58,5 @@ module Capybara
100
58
  "new element."
101
59
  end
102
60
  end
103
-
104
- class TimeoutError < Error
105
- def message
106
- "Timed out waiting for response. It's possible that this happened " \
107
- "because something took a very long time (for example a page load " \
108
- "was slow). If so, setting the Cuprite :timeout option to a higher " \
109
- "value might help."
110
- end
111
- end
112
-
113
- class ScriptTimeoutError < Error
114
- def message
115
- "Timed out waiting for evaluated script to resturn a value"
116
- end
117
- end
118
-
119
- class DeadBrowser < Error
120
- def initialize(message = "Chrome is dead")
121
- super
122
- end
123
- end
124
61
  end
125
62
  end
@@ -123,13 +123,17 @@ class Cuprite {
123
123
  value = value.substr(0, node.maxLength);
124
124
  }
125
125
 
126
+ let valueBefore = node.value;
127
+
126
128
  this.trigger(node, "focus");
127
129
  this.setValue(node, "");
128
130
 
129
131
  if (node.type == "number" || node.type == "date") {
130
132
  this.setValue(node, value);
133
+ this.input(node);
131
134
  } else if (node.type == "time") {
132
135
  this.setValue(node, new Date(value).toTimeString().split(" ")[0]);
136
+ this.input(node);
133
137
  } else if (node.type == "datetime-local") {
134
138
  value = new Date(value);
135
139
  let year = value.getFullYear();
@@ -139,20 +143,29 @@ class Cuprite {
139
143
  let min = ("0" + value.getMinutes()).slice(-2);
140
144
  let sec = ("0" + value.getSeconds()).slice(-2);
141
145
  this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
146
+ this.input(node);
142
147
  } else {
143
148
  for (let i = 0; i < value.length; i++) {
144
149
  let char = value[i];
145
150
  let keyCode = this.characterToKeyCode(char);
146
- this.keyupdowned(node, "keydown", keyCode);
147
- this.setValue(node, node.value + char);
151
+ // call the following functions in order, if one returns false (preventDefault),
152
+ // stop the call chain
153
+ [
154
+ () => this.keyupdowned(node, "keydown", keyCode),
155
+ () => this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)),
156
+ () => {
157
+ this.setValue(node, node.value + char)
158
+ this.input(node)
159
+ }
160
+ ].some(fn => fn())
148
161
 
149
- this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0));
150
162
  this.keyupdowned(node, "keyup", keyCode);
151
163
  }
152
164
  }
153
165
 
154
- this.changed(node);
155
- this.input(node);
166
+ if (valueBefore !== node.value) {
167
+ this.changed(node);
168
+ }
156
169
  this.trigger(node, "blur");
157
170
  }
158
171
 
@@ -172,14 +185,20 @@ class Cuprite {
172
185
  node.dispatchEvent(event);
173
186
  }
174
187
 
188
+ /**
189
+ * @return {boolean} false when an event handler called preventDefault()
190
+ */
175
191
  keyupdowned(node, eventName, keyCode) {
176
192
  let event = document.createEvent("UIEvents");
177
193
  event.initEvent(eventName, true, true);
178
194
  event.keyCode = keyCode;
179
195
  event.charCode = 0;
180
- node.dispatchEvent(event);
196
+ return !node.dispatchEvent(event);
181
197
  }
182
198
 
199
+ /**
200
+ * @return {boolean} false when an event handler called preventDefault()
201
+ */
183
202
  keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
184
203
  event = document.createEvent("UIEvents");
185
204
  event.initEvent("keypress", true, true);
@@ -190,7 +209,7 @@ class Cuprite {
190
209
  event.metaKey = metaKey;
191
210
  event.keyCode = keyCode;
192
211
  event.charCode = charCode;
193
- node.dispatchEvent(event);
212
+ return !node.dispatchEvent(event);
194
213
  }
195
214
 
196
215
  characterToKeyCode(char) {
@@ -450,19 +469,6 @@ class Cuprite {
450
469
  return node.contains(selectedNode);
451
470
  }
452
471
 
453
- isCyclic(object) {
454
- if (Array.isArray(object) && object.every(n => n instanceof Node)) {
455
- return false;
456
- }
457
-
458
- try {
459
- this._json.stringify(object);
460
- return false;
461
- } catch (e) {
462
- return true;
463
- }
464
- }
465
-
466
472
  // This command is purely for testing error handling
467
473
  browserError() {
468
474
  throw new Error("zomg");
@@ -1,24 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Capybara::Cuprite
4
6
  class Node < Capybara::Driver::Node
5
- attr_reader :target_id, :node
7
+ attr_reader :node
6
8
 
7
- def initialize(driver, target_id, node)
8
- super(driver, self)
9
- @target_id, @node = target_id, node
10
- end
9
+ extend Forwardable
11
10
 
12
- def browser
13
- driver.browser
11
+ delegate %i(description) => :node
12
+ delegate %i(browser) => :driver
13
+
14
+ def initialize(driver, node)
15
+ super(driver, self)
16
+ @node = node
14
17
  end
15
18
 
16
19
  def command(name, *args)
17
- browser.send(name, @node, *args)
18
- rescue BrowserError => e
20
+ browser.send(name, node, *args)
21
+ rescue Ferrum::NodeNotFoundError => e
22
+ raise ObsoleteNode.new(self, e.response)
23
+ rescue Ferrum::BrowserError => e
19
24
  case e.message
20
- when "No node with given id found"
21
- raise ObsoleteNode.new(self, e.response)
22
25
  when "Cuprite.MouseEventFailed"
23
26
  raise MouseEventFailed.new(self, e.response)
24
27
  else
@@ -28,13 +31,7 @@ module Capybara::Cuprite
28
31
 
29
32
  def parents
30
33
  command(:parents).map do |parent|
31
- self.class.new(driver, parent["target_id"], parent["node"])
32
- end
33
- end
34
-
35
- def find(method, selector)
36
- command(:find_within, method, selector).map do |node|
37
- self.class.new(driver, @target_id, node)
34
+ self.class.new(driver, parent)
38
35
  end
39
36
  end
40
37
 
@@ -46,6 +43,12 @@ module Capybara::Cuprite
46
43
  find(:css, selector)
47
44
  end
48
45
 
46
+ def find(method, selector)
47
+ command(:find_within, method, selector).map do |node|
48
+ self.class.new(driver, node)
49
+ end
50
+ end
51
+
49
52
  def all_text
50
53
  filter_text(command(:all_text))
51
54
  end
@@ -69,7 +72,8 @@ module Capybara::Cuprite
69
72
  def [](name)
70
73
  # Although the attribute matters, the property is consistent. Return that in
71
74
  # preference to the attribute for links and images.
72
- if ((tag_name == "img") && (name == "src")) || ((tag_name == "a") && (name == "href"))
75
+ if (tag_name == "img" && name == "src") ||
76
+ (tag_name == "a" && name == "href")
73
77
  # if attribute exists get the property
74
78
  return command(:attribute, name) && command(:property, name)
75
79
  end
@@ -121,7 +125,7 @@ module Capybara::Cuprite
121
125
  end
122
126
 
123
127
  def tag_name
124
- @tag_name ||= @node["nodeName"].downcase
128
+ @tag_name ||= description["nodeName"].downcase
125
129
  end
126
130
 
127
131
  def visible?
@@ -141,15 +145,15 @@ module Capybara::Cuprite
141
145
  end
142
146
 
143
147
  def click(keys = [], offset = {})
144
- command(:click, keys, offset)
148
+ prepare_and_click(:left, __method__, keys, offset)
145
149
  end
146
150
 
147
151
  def right_click(keys = [], offset = {})
148
- command(:right_click, keys, offset)
152
+ prepare_and_click(:right, __method__, keys, offset)
149
153
  end
150
154
 
151
155
  def double_click(keys = [], offset = {})
152
- command(:double_click, keys, offset)
156
+ prepare_and_click(:double, __method__, keys, offset)
153
157
  end
154
158
 
155
159
  def hover
@@ -157,7 +161,7 @@ module Capybara::Cuprite
157
161
  end
158
162
 
159
163
  def drag_to(other)
160
- command(:drag, other.node)
164
+ command(:drag, other)
161
165
  end
162
166
 
163
167
  def drag_by(x, y)
@@ -192,10 +196,7 @@ module Capybara::Cuprite
192
196
  end
193
197
 
194
198
  def ==(other)
195
- # We compare backendNodeId because once nodeId is sent to frontend backend
196
- # never returns same nodeId sending 0. In other words frontend is
197
- # responsible for keeping track of node ids.
198
- @target_id == other.target_id && @node["backendNodeId"] == other.node["backendNodeId"]
199
+ node == other.native.node
199
200
  end
200
201
 
201
202
  def send_keys(*keys)
@@ -208,7 +209,7 @@ module Capybara::Cuprite
208
209
  end
209
210
 
210
211
  def inspect
211
- %(#<#{self.class} @target_id=#{@target_id.inspect} @node=#{@node.inspect}>)
212
+ %(#<#{self.class} @node=#{@node.inspect}>)
212
213
  end
213
214
 
214
215
  # @api private
@@ -218,12 +219,17 @@ module Capybara::Cuprite
218
219
 
219
220
  # @api private
220
221
  def as_json(*)
221
- # FIXME: Where this method is used and why attr is called id?
222
- { ELEMENT: { target_id: @target_id, id: @node } }
222
+ # FIXME: Where is this method used and why attr is called id?
223
+ { ELEMENT: { node: node, id: node.node_id } }
223
224
  end
224
225
 
225
226
  private
226
227
 
228
+ def prepare_and_click(mode, name, keys, offset)
229
+ command(:before_click, name, keys, offset)
230
+ node.click(mode: mode, keys: keys, offset: offset)
231
+ end
232
+
227
233
  def filter_text(text)
228
234
  if Capybara::VERSION.to_f < 3
229
235
  Capybara::Helpers.normalize_whitespace(text.to_s)