cuprite 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf587bbc10c2a41b7b147aef2a354a557413ee32a5b692a97c47a48be92aa2ca
4
- data.tar.gz: 77e2b75819381d839138b1b1a6107b8b6851312086aaafe60a00c4ada722bd52
3
+ metadata.gz: d183c86ecad6aaaf681364bda5ee919e5952a2b3a8d55cad40b3a6cec57ff26f
4
+ data.tar.gz: cccf18bb4e24c66262a0a6302ea7611e3aa1897c9b2442c24cfbc0feedfb46b1
5
5
  SHA512:
6
- metadata.gz: 48b56880683350d80c332badd609be3af6ccbe7806ea60b81e4db4cd6bf2501716d5e140d65ab84e74c71376b585097fe8e19d4d139e4bb631d214feb354fcf3
7
- data.tar.gz: 47ce6354f72acf22ca8f337b667481eddc5cb4ad0910b8e96a0a80eb7551fe8d33468c8664ab49b165969fdc1d8f11e5c607f597c354a90624fb7683ed2af5f5
6
+ metadata.gz: '056827d707f4e8388aa2350b53791d060f7c30872d5930311606d750822483a667b5d74093e03217cfa3a342c4fe8cd5d5b766fd3155de9c5ff0c6da6b335a17'
7
+ data.tar.gz: 389f981e6ae4b3a06cc3babbb70b942b0e2a8844bee690fc508af111bab65b333777f7b330554d2ae0f43abae918e3395ff1ae306b17df015efe94eaebd8c0e8
data/README.md CHANGED
@@ -5,8 +5,7 @@
5
5
  Cuprite is a pure Ruby driver (read as _no_ Java/Selenium/WebDriver/ChromeDriver
6
6
  requirement) for [Capybara](https://github.com/teamcapybara/capybara). It allows
7
7
  you to run your Capybara tests on a headless [Chrome](https://www.google.com/chrome/)
8
- or [Chromium](https://www.chromium.org/) browser while the latter is prefered
9
- for now because we work with tip-of-tree [protocol](https://chromedevtools.github.io/devtools-protocol/).
8
+ or [Chromium](https://www.chromium.org/) browser by [CDP protocol](https://chromedevtools.github.io/devtools-protocol/).
10
9
 
11
10
  The emphasis was made on raw CDP protocol because Headless Chrome allows you to
12
11
  do so many cool things that are barely supported by WebDriver because it should
@@ -21,8 +20,8 @@ Poltergest/PhantomJS:
21
20
 
22
21
  ```
23
22
  cuprite:
24
- Finished in 8 minutes 44 seconds (files took 0.97501 seconds to load)
25
- 1531 examples, 0 failures, 112 pending
23
+ Finished in 8 minutes 58 seconds (files took 0.89959 seconds to load)
24
+ 1555 examples, 0 failures, 27 pending
26
25
 
27
26
  selenium headless chrome:
28
27
  Finished in 9 minutes 13 seconds (files took 0.97749 seconds to load)
@@ -39,11 +38,6 @@ Finished in 11 minutes 49 seconds (files took 0.54019 seconds to load)
39
38
  gem "cuprite"
40
39
  ```
41
40
 
42
- Though we recommend using github until we release 1.0:
43
- ``` ruby
44
- gem "cuprite", github: "machinio/cuprite"
45
- ```
46
-
47
41
  and run `bundle install`.
48
42
 
49
43
  In your test setup add:
@@ -137,13 +131,25 @@ end
137
131
 
138
132
  `options` is a hash of options. The following options are supported:
139
133
 
140
- * `:browser` (Hash) - Hash of options to be passed to chrome process:
141
- * `:path` (String) - Path to chrome binary, you can also set ENV variable as
142
- `BROWSER_PATH=some/path/chrome bundle exec rspec`
143
- * `:window_size` (Array) - The dimensions of the browser window in which to
134
+ * `:browser_path` (String) - Path to chrome binary, you can also set ENV
135
+ variable as `BROWSER_PATH=some/path/chrome bundle exec rspec`.
136
+ * `:headless` (Boolean) - Set browser as headless or not, `true` by default.
137
+ * `:logger` (Object responding to `puts`) - When present, debug output is
138
+ written to this object.
139
+ * `:timeout` (Numeric) - The number of seconds we'll wait for a response when
140
+ communicating with browser. Default is 30.
141
+ * `:js_errors` (Boolean) - When true, JavaScript errors get re-raised in Ruby.
142
+ * `:window_size` (Array) - The dimensions of the browser window in which to
144
143
  test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
145
- * `:port` (Integer) - Remote debugging port for headless Chrome
146
- * `:host` (String) - Remote debugging address for headless Chrome
144
+ * `:browser_options` (Hash) - Additional command line options,
145
+ [see them all](https://peter.sh/experiments/chromium-command-line-switches/)
146
+ e.g. `{ "ignore-certificate-errors" => nil }`
147
+ * `:extensions` (Array) - An array of JS files to be preloaded into the browser
148
+ * `:port` (Integer) - Remote debugging port for headless Chrome
149
+ * `:host` (String) - Remote debugging address for headless Chrome
150
+ * `:url_blacklist` (Array) - array of strings to match against requested URLs
151
+ * `:url_whitelist` (Array) - array of strings to match against requested URLs
152
+
147
153
 
148
154
  ### URL Blacklisting & Whitelisting ###
149
155
  Cuprite supports URL blacklisting, which allows you to prevent scripts from
@@ -34,12 +34,13 @@ module Capybara::Cuprite
34
34
  go_back go_forward find_modal accept_confirm dismiss_confirm
35
35
  accept_prompt dismiss_prompt reset_modals) => :page
36
36
 
37
- attr_reader :process, :logger
37
+ attr_reader :process, :logger, :js_errors
38
38
  attr_writer :timeout
39
39
 
40
40
  def initialize(options = nil)
41
41
  @options = Hash(options)
42
42
  @logger, @timeout = @options.values_at(:logger, :timeout)
43
+ @js_errors = @options.fetch(:js_errors, false)
43
44
 
44
45
  if ENV["CUPRITE_DEBUG"]
45
46
  STDOUT.sync = true
@@ -79,16 +80,7 @@ module Capybara::Cuprite
79
80
  end
80
81
 
81
82
  def visible_text(node)
82
- begin
83
- evaluate_on(node: node, expr: "_cuprite.visibleText(this)")
84
- rescue BrowserError => e
85
- # FIXME: ObsoleteNode first arg is node, so it should be in node class
86
- if e.message == "No node with given id found"
87
- raise ObsoleteNode.new(self, e.response)
88
- end
89
-
90
- raise
91
- end
83
+ evaluate_on(node: node, expr: "_cuprite.visibleText(this)")
92
84
  end
93
85
 
94
86
  def delete_text(node)
@@ -96,32 +88,36 @@ module Capybara::Cuprite
96
88
  end
97
89
 
98
90
  def select_file(node, value)
99
- raise NotImplementedError
91
+ page.command("DOM.setFileInputFiles", nodeId: node["nodeId"], files: Array(value))
100
92
  end
101
93
 
102
- def render(path, _options = {})
103
- # check_render_options!(options)
104
- # options[:full] = !!options[:full]
105
- data = Base64.decode64(render_base64)
106
- File.open(path.to_s, "wb") { |f| f.write(data) }
94
+ def render(path, options = {})
95
+ format = options.delete(:format)
96
+ options = options.merge(path: path)
97
+ bin = Base64.decode64(render_base64(format, options))
98
+ File.open(path.to_s, "wb") { |f| f.write(bin) }
107
99
  end
108
100
 
109
- def render_base64(format = "png", _options = {})
110
- # check_render_options!(options)
111
- # options[:full] = !!options[:full]
112
- page.command("Page.captureScreenshot", format: format)["data"]
101
+ def render_base64(format, options = {})
102
+ options = render_options(format, options)
103
+
104
+ if options[:format].to_s == "pdf"
105
+ options = {}
106
+ options[:paperWidth] = @paper_size[:width].to_f if @paper_size
107
+ options[:paperHeight] = @paper_size[:height].to_f if @paper_size
108
+ options[:scale] = @zoom_factor if @zoom_factor
109
+ page.command("Page.printToPDF", **options)
110
+ else
111
+ page.command("Page.captureScreenshot", **options)
112
+ end.fetch("data")
113
113
  end
114
114
 
115
115
  def set_zoom_factor(zoom_factor)
116
- raise NotImplementedError
116
+ @zoom_factor = zoom_factor.to_f
117
117
  end
118
118
 
119
119
  def set_paper_size(size)
120
- raise NotImplementedError
121
- end
122
-
123
- def set_proxy(ip, port, type, user, password)
124
- raise NotImplementedError
120
+ @paper_size = size
125
121
  end
126
122
 
127
123
  def headers=(headers)
@@ -198,6 +194,7 @@ module Capybara::Cuprite
198
194
 
199
195
  def reset
200
196
  @headers = {}
197
+ @zoom_factor = nil
201
198
  targets.reset
202
199
  end
203
200
 
@@ -216,6 +213,10 @@ module Capybara::Cuprite
216
213
  command("Browser.crash")
217
214
  end
218
215
 
216
+ def browser_error
217
+ page.evaluate("_cuprite.browserError()")
218
+ end
219
+
219
220
  def command(*args)
220
221
  id = @client.command(*args)
221
222
  @client.wait(id: id)
@@ -236,10 +237,36 @@ module Capybara::Cuprite
236
237
  @client = Client.new(self, @process.ws_url)
237
238
  end
238
239
 
239
- def check_render_options!(options)
240
- return if !options[:full] || !options.key?(:selector)
241
- warn "Ignoring :selector in #render since :full => true was given at #{caller(1..1).first}"
242
- options.delete(:selector)
240
+ def render_options(format, opts)
241
+ options = {}
242
+
243
+ format ||= File.extname(opts[:path]).delete(".") || "png"
244
+ format = "jpeg" if format == "jpg"
245
+ raise "Not supported format: #{format}. jpeg | png | pdf" if format !~ /jpeg|png|pdf/i
246
+ options.merge!(format: format)
247
+
248
+ options.merge!(quality: opts[:quality] ? opts[:quality] : 75) if format == "jpeg"
249
+
250
+ warn "Ignoring :selector in #render since full: true was given at #{caller(1..1).first}" if !!opts[:full] && opts[:selector]
251
+
252
+ if !!opts[:full]
253
+ width, height = page.evaluate("[document.documentElement.offsetWidth, document.documentElement.offsetHeight]")
254
+ options.merge!(clip: { x: 0, y: 0, width: width, height: height, scale: @zoom_factor || 1.0 }) if width > 0 && height > 0
255
+ elsif opts[:selector]
256
+ rect = page.evaluate("document.querySelector('#{opts[:selector]}').getBoundingClientRect()")
257
+ options.merge!(clip: { x: rect["x"], y: rect["y"], width: rect["width"], height: rect["height"], scale: @zoom_factor || 1.0 })
258
+ end
259
+
260
+ if @zoom_factor
261
+ if !options[:clip]
262
+ width, height = page.evaluate("[document.documentElement.clientWidth, document.documentElement.clientHeight]")
263
+ options[:clip] = { x: 0, y: 0, width: width, height: height }
264
+ end
265
+
266
+ options[:clip].merge!(scale: @zoom_factor)
267
+ end
268
+
269
+ options
243
270
  end
244
271
 
245
272
  def find_all(method, selector, within = nil)
@@ -75,8 +75,8 @@ module Capybara::Cuprite
75
75
  evaluate_on(node: node, expr: %(_cuprite.trigger(this, "#{event}")), **options)
76
76
  end
77
77
 
78
- def scroll_to(left, top)
79
- raise NotImplementedError
78
+ def scroll_to(top, left)
79
+ execute("window.scrollTo(#{top}, #{left})")
80
80
  end
81
81
 
82
82
  def send_keys(node, keys)
@@ -171,7 +171,16 @@ module Capybara::Cuprite
171
171
  end
172
172
 
173
173
  def get_content_quads(node)
174
- result = command("DOM.getContentQuads", nodeId: node["nodeId"])
174
+ begin
175
+ result = command("DOM.getContentQuads", nodeId: node["nodeId"])
176
+ rescue BrowserError => e
177
+ if e.message == "Could not compute content quads."
178
+ raise MouseEventFailed.new("MouseEventFailed: click, none, 0, 0")
179
+ else
180
+ raise
181
+ end
182
+ end
183
+
175
184
  raise "Node is either not visible or not an HTMLElement" if result["quads"].size == 0
176
185
 
177
186
  # FIXME: Case when a few quads returned
@@ -24,7 +24,7 @@ class Cuprite {
24
24
  results.push(xpath.snapshotItem(i));
25
25
  }
26
26
  } else {
27
- results = within.querySelectorAll(selector);
27
+ results = Array.from(within.querySelectorAll(selector));
28
28
  }
29
29
 
30
30
  return results;
@@ -441,6 +441,10 @@ class Cuprite {
441
441
  }
442
442
 
443
443
  isCyclic(object) {
444
+ if (Array.isArray(object) && object.every(n => n instanceof Node)) {
445
+ return false;
446
+ }
447
+
444
448
  try {
445
449
  this._json.stringify(object);
446
450
  return false;
@@ -448,6 +452,11 @@ class Cuprite {
448
452
  return true;
449
453
  }
450
454
  }
455
+
456
+ // This command is purely for testing error handling
457
+ browserError() {
458
+ throw new Error("zomg");
459
+ }
451
460
  }
452
461
 
453
462
  window._cuprite = new Cuprite;
@@ -90,11 +90,17 @@ module Capybara::Cuprite
90
90
  @client.close
91
91
  end
92
92
 
93
- def resize(width, height)
93
+ def resize(width: nil, height: nil, fullscreen: false)
94
94
  result = @browser.command("Browser.getWindowForTarget", targetId: @target_id)
95
95
  @window_id, @bounds = result.values_at("windowId", "bounds")
96
- @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { width: width, height: height })
97
- command("Emulation.setDeviceMetricsOverride", width: width, height: height, deviceScaleFactor: 1, mobile: false)
96
+
97
+ if fullscreen
98
+ @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { windowState: "fullscreen" })
99
+ else
100
+ @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { windowState: "normal" })
101
+ @browser.command("Browser.setWindowBounds", windowId: @window_id, bounds: { width: width, height: height, windowState: "normal" })
102
+ command("Emulation.setDeviceMetricsOverride", width: width, height: height, deviceScaleFactor: 1, mobile: false)
103
+ end
98
104
  end
99
105
 
100
106
  def refresh
@@ -182,7 +188,7 @@ module Capybara::Cuprite
182
188
  @wait = 0
183
189
  end
184
190
 
185
- response = @client.wait(id: id)
191
+ @client.wait(id: id)
186
192
  end
187
193
 
188
194
  private
@@ -196,16 +202,27 @@ module Capybara::Cuprite
196
202
  end
197
203
  end
198
204
 
205
+ if @browser.js_errors
206
+ @client.subscribe("Runtime.exceptionThrown") do |params|
207
+ Thread.main.raise JavaScriptError.new(params.dig("exceptionDetails", "exception"))
208
+ end
209
+ end
210
+
199
211
  @client.subscribe("Page.javascriptDialogOpening") do |params|
200
212
  accept_modal = @accept_modal.last
201
213
  if accept_modal == true || accept_modal == false
202
214
  @accept_modal.pop
203
215
  @modal_messages << params["message"]
204
216
  options = { accept: accept_modal }
205
- default = params["defaultPrompt"]
206
217
  response = @modal_response || params["defaultPrompt"]
207
218
  options.merge!(promptText: response) if response
208
219
  @client.command("Page.handleJavaScriptDialog", **options)
220
+ else
221
+ warn "Modal window has been opened, but you didn't wrap your code into (`accept_prompt` | `dismiss_prompt` | `accept_confirm` | `dismiss_confirm` | `accept_alert`), accepting by default"
222
+ options = { accept: true }
223
+ response = params["defaultPrompt"]
224
+ options.merge!(promptText: response) if response
225
+ @client.command("Page.handleJavaScriptDialog", **options)
209
226
  end
210
227
  end
211
228
 
@@ -280,6 +297,9 @@ module Capybara::Cuprite
280
297
  command("Runtime.enable")
281
298
  command("Log.enable")
282
299
  command("Network.enable")
300
+ if Capybara.save_path
301
+ command("Page.setDownloadBehavior", behavior: "allow", downloadPath: Capybara.save_path)
302
+ end
283
303
 
284
304
  @browser.extensions.each do |extension|
285
305
  @client.command("Page.addScriptToEvaluateOnNewDocument", source: extension)
@@ -53,20 +53,27 @@ module Capybara::Cuprite
53
53
  end
54
54
 
55
55
  def initialize(options)
56
- @options = options.fetch(:browser, {})
56
+ @options = {}
57
57
 
58
- detect_browser_path
58
+ detect_browser_path(options)
59
59
 
60
60
  window_size = options.fetch(:window_size, [1024, 768])
61
- @options = @options.merge("window-size" => window_size.join(","))
61
+ @options.merge!("window-size" => window_size.join(","))
62
62
 
63
63
  port = options.fetch(:port, BROWSER_PORT)
64
- @options = @options.merge("remote-debugging-port" => port)
64
+ @options.merge!("remote-debugging-port" => port)
65
65
 
66
66
  host = options.fetch(:host, BROWSER_HOST)
67
- @options = @options.merge("remote-debugging-address" => host)
67
+ @options.merge!("remote-debugging-address" => host)
68
68
 
69
69
  @options = DEFAULT_OPTIONS.merge(@options)
70
+
71
+ unless options.fetch(:headless, true)
72
+ @options.delete("headless")
73
+ @options.delete("disable-gpu")
74
+ end
75
+
76
+ @options.merge!(options.fetch(:browser_options, {}))
70
77
  end
71
78
 
72
79
  def start
@@ -99,15 +106,24 @@ module Capybara::Cuprite
99
106
  start
100
107
  end
101
108
 
102
- def detect_browser_path
103
- exe = @options[:path] || BROWSER_PATH
104
- if RUBY_PLATFORM.include?('darwin')
105
- exe ||= "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
106
- @path = exe if File.exist?(exe)
107
- else
108
- exe ||= "chrome"
109
- @path = Cliver.detect(exe) || Cliver.detect("google-chrome")
110
- end
109
+ private
110
+
111
+ def detect_browser_path(options)
112
+ @path =
113
+ options[:browser_path] ||
114
+ BROWSER_PATH || (
115
+ if RUBY_PLATFORM.include?('darwin')
116
+ [
117
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
118
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
119
+ ].find { |path| File.exist?(path) }
120
+ else
121
+ %w[chromium google-chrome-unstable google-chrome-beta google-chrome chrome].reduce(nil) do |path, exe|
122
+ path = Cliver.detect(exe)
123
+ break path if path
124
+ end
125
+ end
126
+ )
111
127
 
112
128
  unless @path
113
129
  message = "Could not find an executable `#{exe}`. Try to make it " \
@@ -117,8 +133,6 @@ module Capybara::Cuprite
117
133
  end
118
134
  end
119
135
 
120
- private
121
-
122
136
  def redirect_stdout(write_io)
123
137
  if Capybara::Cuprite.mri?
124
138
  yield
@@ -76,8 +76,19 @@ module Capybara::Cuprite
76
76
  options = options.merge(executionContextId: execution_context_id)
77
77
  end
78
78
 
79
- command("Runtime.callFunctionOn", **options)
80
- .dig("result").tap { |r| handle_error(r) }
79
+ begin
80
+ attempts ||= 1
81
+ response = command("Runtime.callFunctionOn", **options)
82
+ response.dig("result").tap { |r| handle_error(r) }
83
+ rescue BrowserError => e
84
+ case e.message
85
+ when "No node with given id found", "Could not find node with given id", "Cannot find context with specified id"
86
+ sleep 0.1
87
+ attempts += 1
88
+ options = options.merge(executionContextId: execution_context_id)
89
+ retry if attempts <= 3
90
+ end
91
+ end
81
92
  end
82
93
 
83
94
  # FIXME: We should have a central place to handle all type of errors
@@ -107,7 +107,14 @@ module Capybara::Cuprite
107
107
  end
108
108
 
109
109
  def targets
110
- @browser.command("Target.getTargets")["targetInfos"]
110
+ attempts ||= 1
111
+ # Targets cannot be empty the must be at least one default target.
112
+ targets = @browser.command("Target.getTargets")["targetInfos"]
113
+ raise TypeError if targets.empty?
114
+ targets
115
+ rescue TypeError
116
+ attempts += 1
117
+ retry if attempts < 3
111
118
  end
112
119
 
113
120
  def default?(target)
@@ -14,7 +14,7 @@ module Capybara::Cuprite
14
14
 
15
15
  def initialize(app, options = {})
16
16
  @app = app
17
- @options = options.freeze
17
+ @options = options
18
18
  @started = false
19
19
  end
20
20
 
@@ -152,7 +152,7 @@ module Capybara::Cuprite
152
152
  end
153
153
 
154
154
  def resize(width, height)
155
- browser.resize(width, height)
155
+ browser.resize(width: width, height: height)
156
156
  end
157
157
  alias_method :resize_window, :resize
158
158
 
@@ -172,6 +172,12 @@ module Capybara::Cuprite
172
172
  end
173
173
  end
174
174
 
175
+ def fullscreen_window(handle)
176
+ within_window(handle) do
177
+ browser.resize(fullscreen: true)
178
+ end
179
+ end
180
+
175
181
  def scroll_to(left, top)
176
182
  browser.scroll_to(left, top)
177
183
  end
@@ -184,8 +190,11 @@ module Capybara::Cuprite
184
190
  browser.clear_network_traffic
185
191
  end
186
192
 
187
- def set_proxy(ip, port, type = "http", user = nil, password = nil)
188
- browser.set_proxy(ip, port, type, user, password)
193
+ # FIXME: check user/pass proxy auth
194
+ def set_proxy(ip, port, type = "http", user = nil, password = nil, bypass = nil)
195
+ @options[:browser] ||= {}
196
+ @options[:browser].merge!("proxy-server" => "#{type}=#{ip}:#{port}")
197
+ @options[:browser].merge!("proxy-bypass-list" => bypass) if bypass
189
198
  end
190
199
 
191
200
  def headers
@@ -17,7 +17,7 @@ module Capybara::Cuprite
17
17
  browser.send(name, @node, *args)
18
18
  rescue BrowserError => e
19
19
  case e.message
20
- when "Cuprite.ObsoleteNode"
20
+ when "No node with given id found"
21
21
  raise ObsoleteNode.new(self, e.response)
22
22
  when "Cuprite.MouseEventFailed"
23
23
  raise MouseEventFailed.new(self, e.response)
@@ -168,6 +168,29 @@ module Capybara::Cuprite
168
168
  command(:trigger, event)
169
169
  end
170
170
 
171
+ def scroll_to(element, location, position = nil)
172
+ if element.is_a?(Node)
173
+ scroll_element_to_location(element, location)
174
+ elsif location.is_a?(Symbol)
175
+ scroll_to_location(location)
176
+ else
177
+ scroll_to_coords(*position)
178
+ end
179
+ self
180
+ end
181
+
182
+ def scroll_by(x, y)
183
+ driver.execute_script <<~JS, self, x, y
184
+ var el = arguments[0];
185
+ if (el.scrollBy){
186
+ el.scrollBy(arguments[1], arguments[2]);
187
+ } else {
188
+ el.scrollTop = el.scrollTop + arguments[2];
189
+ el.scrollLeft = el.scrollLeft + arguments[1];
190
+ }
191
+ JS
192
+ end
193
+
171
194
  def ==(other)
172
195
  # We compare backendNodeId because once nodeId is sent to frontend backend
173
196
  # never returns same nodeId sending 0. In other words frontend is
@@ -212,5 +235,50 @@ module Capybara::Cuprite
212
235
  .tr("\u00a0", " ")
213
236
  end
214
237
  end
238
+
239
+ def scroll_element_to_location(element, location)
240
+ scroll_opts = case location
241
+ when :top
242
+ 'true'
243
+ when :bottom
244
+ 'false'
245
+ when :center
246
+ "{behavior: 'instant', block: 'center'}"
247
+ else
248
+ raise ArgumentError, "Invalid scroll_to location: #{location}"
249
+ end
250
+ driver.execute_script <<~JS, element
251
+ arguments[0].scrollIntoView(#{scroll_opts})
252
+ JS
253
+ end
254
+
255
+ def scroll_to_location(location)
256
+ scroll_y = case location
257
+ when :top
258
+ '0'
259
+ when :bottom
260
+ 'arguments[0].scrollHeight'
261
+ when :center
262
+ '(arguments[0].scrollHeight - arguments[0].clientHeight)/2'
263
+ end
264
+ driver.execute_script <<~JS, self
265
+ if (arguments[0].scrollTo){
266
+ arguments[0].scrollTo(0, #{scroll_y});
267
+ } else {
268
+ arguments[0].scrollTop = #{scroll_y};
269
+ }
270
+ JS
271
+ end
272
+
273
+ def scroll_to_coords(x, y)
274
+ driver.execute_script <<~JS, self, x, y
275
+ if (arguments[0].scrollTo){
276
+ arguments[0].scrollTo(arguments[1], arguments[2]);
277
+ } else {
278
+ arguments[0].scrollTop = arguments[2];
279
+ arguments[0].scrollLeft = arguments[1];
280
+ }
281
+ JS
282
+ end
215
283
  end
216
284
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Cuprite
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuprite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Vorotilin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-25 00:00:00.000000000 Z
11
+ date: 2019-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -176,6 +176,20 @@ dependencies:
176
176
  - - "~>"
177
177
  - !ruby/object:Gem::Version
178
178
  version: '3.0'
179
+ - !ruby/object:Gem::Dependency
180
+ name: chunky_png
181
+ requirement: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - "~>"
184
+ - !ruby/object:Gem::Version
185
+ version: '1.3'
186
+ type: :development
187
+ prerelease: false
188
+ version_requirements: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - "~>"
191
+ - !ruby/object:Gem::Version
192
+ version: '1.3'
179
193
  description: Cuprite is a driver for Capybara that allows you to run your tests on
180
194
  a headless Chrome browser
181
195
  email: