cuprite 0.3.0 → 0.4.0

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.
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: