cuprite 0.7.1 → 0.12

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: 61e6eae56b712430083dd1dc25ae7888016899b9504fcb2795525fe2df0d9b63
4
- data.tar.gz: 26f55efb799bff8ffa704fd1955dc01b03c11db3f44806f3a1c8284d84237ae8
3
+ metadata.gz: bac1e4393d6796d34198e45428ea8d443b67136e4805e77f7c7d3f35451cefbb
4
+ data.tar.gz: 27edf7157ac47b0a6bbc579e7908aaa0313d91b7f3702417453f76f1230e882d
5
5
  SHA512:
6
- metadata.gz: a8eb4c7caeeaf86f24ebef9bf37edf6bf3e6ccf474633be8c69a1b4296abfcb5df040ede0c5feccecc930393312408800a928f3671aeb658a7d5beadb08a3c0a
7
- data.tar.gz: 44387ad8fa9b9a87a236d2df3d754155ad4ed280348d65da2479b1a5a08e6be215ebf2ad652c1159188f08f6f231fbeeeddd2ce767d5bc9a53b1c0e7f39fbdf1
6
+ metadata.gz: a8ed43b593e4a071e449a80ac92925abafff1d9afa2a3934783963a19411deddcc18b1906467d5d355a93ed891ec4aae4b21509031190020d154202ba21904e8
7
+ data.tar.gz: ed39a7d445203f93b402fc313caa883449d7e743a6690196ba7f6bed9ee1d8c506f06d4b8ae620c8bc9d1c21c5770458b88564e9e937f7b5583e01a931fece39
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2018 Machinio
3
+ Copyright (c) 2018-2021 Dmitry Vorotilin
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,26 +1,25 @@
1
- # Cuprite - Headless Chrome driver for Capybara #
1
+ # Cuprite - Headless Chrome driver for Capybara
2
2
 
3
- [![Build Status](https://travis-ci.org/machinio/cuprite.svg?branch=master)](https://travis-ci.org/machinio/cuprite)
3
+ Cuprite is a pure Ruby driver (read as _no_ Selenium/WebDriver/ChromeDriver
4
+ dependency) for [Capybara](https://github.com/teamcapybara/capybara). It allows
5
+ you to run Capybara tests on a headless Chrome or Chromium. Under the hood it
6
+ uses [Ferrum](https://github.com/rubycdp/ferrum#index) which is high-level API
7
+ to the browser by CDP protocol. The design of the driver is as close to
8
+ [Poltergeist](https://github.com/teampoltergeist/poltergeist) as possible though
9
+ it's not a goal.
4
10
 
5
- Cuprite is a pure Ruby driver (read as _no_ Java/Selenium/WebDriver/ChromeDriver
6
- requirement) for [Capybara](https://github.com/teamcapybara/capybara). It allows
7
- you to run your Capybara tests on a headless [Chrome](https://www.google.com/chrome/)
8
- or [Chromium](https://www.chromium.org/) by [CDP protocol](https://chromedevtools.github.io/devtools-protocol/).
9
- Under the hood it uses [Ferrum](https://github.com/route/ferrum) which is
10
- high-level API to the browser again by CDP protocol.
11
+ [Cuprite](https://evrone.com/cuprite) designed & supported by [Evrone](https://evrone.com/)
12
+ What else we build [with Ruby](https://evrone.com/ruby)
11
13
 
12
- The emphasis was made on raw CDP protocol because Headless Chrome allows you to
13
- do so many things that are barely supported by WebDriver because it should have
14
- consistent design with other browsers. The design of the driver will be as
15
- close to [Poltergeist](https://github.com/teampoltergeist/poltergeist) as
16
- possible though it's not a goal.
17
14
 
18
- ## Install ##
15
+ ## Install
19
16
 
20
- Add these lines to your `Gemfile` and run `bundle install`.
17
+ Add this to your `Gemfile` and run `bundle install`.
21
18
 
22
19
  ``` ruby
23
- gem "cuprite", group: :test
20
+ group :test do
21
+ gem "cuprite"
22
+ end
24
23
  ```
25
24
 
26
25
  In your test setup add:
@@ -33,21 +32,31 @@ Capybara.register_driver(:cuprite) do |app|
33
32
  end
34
33
  ```
35
34
 
36
- If you already had tests on Poltergeist then it should simply work, for Selenium
37
- you better check your code for `.manage` calls because things are much easier
38
- with Cuprite, see the documentation below.
35
+ if you use `Docker` don't forget to pass `no-sandbox` option:
36
+
37
+ ```ruby
38
+ Capybara::Cuprite::Driver.new(app, browser_options: { 'no-sandbox': nil })
39
+ ```
40
+
41
+ Since Cuprite uses [Ferrum](https://github.com/rubycdp/ferrum#examples) there
42
+ are many useful methods you can call even using this driver:
43
+
44
+ ```ruby
45
+ browser = page.driver.browser
46
+ browser.mouse.move(x: 123, y: 456).down.up
47
+ ```
48
+
49
+ If you already have tests on Poltergeist then it should simply work, for
50
+ Selenium you better check your code for `manage` calls because it works
51
+ differently in Cuprite, see the documentation below.
39
52
 
40
- ## Install Chrome ##
41
53
 
42
- There's no official Chrome or Chromium package for Linux don't install it this
43
- way because it either will be outdated or unofficial, both are bad. Download it
44
- from official [source](https://www.chromium.org/getting-involved/download-chromium).
45
- Chrome binary should be in the `PATH` or `BROWSER_PATH` or you can pass it as an
46
- option
54
+ ## Customization
47
55
 
48
- ## Customization ##
56
+ See the full list of options for
57
+ [Ferrum](https://github.com/rubycdp/ferrum#customization).
49
58
 
50
- You can customize options with the following code in your test setup:
59
+ You can pass options with the following code in your test setup:
51
60
 
52
61
  ``` ruby
53
62
  Capybara.register_driver(:cuprite) do |app|
@@ -55,47 +64,52 @@ Capybara.register_driver(:cuprite) do |app|
55
64
  end
56
65
  ```
57
66
 
58
- #### Running in Docker ####
67
+ `Cuprite`-specific options are:
68
+
69
+ * options `Hash`
70
+ * `:url_blacklist` (Array) - array of strings to match against requested URLs
71
+ * `:url_whitelist` (Array) - array of strings to match against requested URLs
72
+
73
+
74
+ ## Debugging
59
75
 
60
- In docker as root you must pass the no-sandbox browser option:
76
+ If you pass `inspector` option, remote debugging will be enabled if you run
77
+ tests with `INSPECTOR=true`. Then you can put `page.driver.debug` or
78
+ `page.driver.debug(binding)` in your test to pause it. This will launch the
79
+ browser where you can inspect the content.
61
80
 
62
81
  ```ruby
63
- Capybara::Cuprite::Driver.new(app, browser_options: { 'no-sandbox': nil })
82
+ Capybara.register_driver :cuprite do |app|
83
+ Capybara::Cuprite::Driver.new(app, inspector: ENV['INSPECTOR'])
84
+ end
64
85
  ```
65
86
 
66
- * options `Hash`
67
- * `:browser_path` (String) - Path to chrome binary, you can also set ENV
68
- variable as `BROWSER_PATH=some/path/chrome bundle exec rspec`.
69
- * `:headless` (Boolean) - Set browser as headless or not, `true` by default.
70
- * `:slowmo` (Integer | Float) - Set a delay to wait before sending command.
71
- Usefull companion of headless option, so that you have time to see changes.
72
- * `:logger` (Object responding to `puts`) - When present, debug output is
73
- written to this object.
74
- * `:timeout` (Numeric) - The number of seconds we'll wait for a response when
75
- communicating with browser. Default is 5.
76
- * `:js_errors` (Boolean) - When true, JavaScript errors get re-raised in Ruby.
77
- * `:window_size` (Array) - The dimensions of the browser window in which to
78
- test, expressed as a 2-element array, e.g. [1024, 768]. Default: [1024, 768]
79
- * `:browser_options` (Hash) - Additional command line options,
80
- [see them all](https://peter.sh/experiments/chromium-command-line-switches/)
81
- e.g. `{ "ignore-certificate-errors" => nil }`
82
- * `:extensions` (Array) - An array of JS files to be preloaded into the browser
83
- * `:port` (Integer) - Remote debugging port for headless Chrome
84
- * `:host` (String) - Remote debugging address for headless Chrome
85
- * `:url` (String) - URL for a running instance of Chrome. If this is set, a
86
- browser process will not be spawned.
87
- * `:url_blacklist` (Array) - array of strings to match against requested URLs
88
- * `:url_whitelist` (Array) - array of strings to match against requested URLs
89
- * `:process_timeout` (Integer) - How long to wait for the Chrome process to
90
- respond on startup
87
+ then somewhere in the test:
88
+
89
+ ```ruby
90
+ it "does something useful" do
91
+ visit root_path
92
+
93
+ fill_in "field", with: "value"
94
+ page.driver.debug(binding)
95
+
96
+ expect(page).to have_content("value")
97
+ end
98
+ ```
99
+
100
+ In the middle of the execution Chrome will open a new tab where you can inspect
101
+ the content and also if you passed `binding` an `irb` or `pry` console will be
102
+ opened where you can further experiment with the test.
91
103
 
92
- ### Clicking/Scrolling ###
104
+
105
+ ## Clicking/Scrolling
93
106
 
94
107
  * `page.driver.click(x, y)` Click a very specific area of the screen.
95
- * `page.driver.scroll_to(left, top)` Scroll to given position.
96
- * `element.send_keys(*keys)` Send keys to given node.
108
+ * `page.driver.scroll_to(left, top)` Scroll to a given position.
109
+ * `element.send_keys(*keys)` Send keys to a given node.
110
+
97
111
 
98
- ### Request headers ###
112
+ ## Request headers
99
113
 
100
114
  Manipulate HTTP request headers like a boss:
101
115
 
@@ -111,11 +125,11 @@ Notice that `headers=` will overwrite already set headers. You should use
111
125
  subsequent HTTP requests (including requests for assets, AJAX, etc). They will
112
126
  be automatically cleared at the end of the test.
113
127
 
114
- ### Network traffic ###
115
128
 
116
- * `page.driver.network_traffic` Inspect network traffic (resources have been
117
- loaded) on the current page. This returns an array of request objects.
129
+ ## Network traffic
118
130
 
131
+ * `page.driver.network_traffic` Inspect network traffic (loaded resources) on
132
+ the current page. This returns an array of request objects.
119
133
 
120
134
  ```ruby
121
135
  page.driver.network_traffic # => [Request, ...]
@@ -123,11 +137,30 @@ request = page.driver.network_traffic.first
123
137
  request.response
124
138
  ```
125
139
 
140
+ * `page.driver.wait_for_network_idle` Natively waits for network idle and if
141
+ there are no active connections returns or raises `TimeoutError` error. Accepts
142
+ the same options as
143
+ [`wait_for_idle`](https://github.com/rubycdp/ferrum#wait_for_idleoptions)
144
+
145
+ ```ruby
146
+ page.driver.wait_for_network_idle
147
+ page.driver.refresh
148
+ ```
149
+
126
150
  Please note that network traffic is not cleared when you visit new page. You can
127
151
  manually clear the network traffic by calling `page.driver.clear_network_traffic`
128
152
  or `page.driver.reset`
129
153
 
130
- ### Manipulating cookies ###
154
+ * `page.driver.wait_for_reload` unlike `wait_for_network_idle` will wait until
155
+ the whole page is reloaded or raise a timeout error. It's useful when you know
156
+ that for example after clicking autocomplete suggestion you expect page to be
157
+ reloaded, you have a few choices - put sleep or wait for network idle, but both
158
+ are bad. Sleep makes you wait longer or less than needed, network idle can
159
+ return earlier even before the whole page is started to reload. Here's the
160
+ rescue.
161
+
162
+
163
+ ## Manipulating cookies
131
164
 
132
165
  The following methods are used to inspect and manipulate cookies:
133
166
 
@@ -142,18 +175,22 @@ The following methods are used to inspect and manipulate cookies:
142
175
  * `page.driver.remove_cookie(name)` - remove a cookie
143
176
  * `page.driver.clear_cookies` - clear all cookies
144
177
 
145
- ### Screenshot ###
178
+
179
+ ## Screenshot
146
180
 
147
181
  Besides capybara screenshot method you can get image as Base64:
148
182
 
149
183
  * `page.driver.render_base64(format, options)`
150
184
 
151
- ### Authorization ###
185
+
186
+ ## Authorization
152
187
 
153
188
  * `page.driver.basic_authorize(user, password)`
154
189
  * `page.driver.set_proxy(ip, port, type, user, password)`
155
190
 
156
- ### URL Blacklisting & Whitelisting ###
191
+
192
+ ## URL Blacklisting & Whitelisting
193
+
157
194
  Cuprite supports URL blacklisting, which allows you to prevent scripts from
158
195
  running on designated domains:
159
196
 
@@ -161,8 +198,8 @@ running on designated domains:
161
198
  page.driver.browser.url_blacklist = ["http://www.example.com"]
162
199
  ```
163
200
 
164
- and also URL whitelisting, which allows scripts to only run
165
- on designated domains:
201
+ and also URL whitelisting, which allows scripts to only run on designated
202
+ domains:
166
203
 
167
204
  ```ruby
168
205
  page.driver.browser.url_whitelist = ["http://www.example.com"]
@@ -171,41 +208,3 @@ page.driver.browser.url_whitelist = ["http://www.example.com"]
171
208
  If you are experiencing slower run times, consider creating a URL whitelist of
172
209
  domains that are essential or a blacklist of domains that are not essential,
173
210
  such as ad networks or analytics, to your testing environment.
174
-
175
- ### Remote debugging ###
176
-
177
- If you use the `inspector: true` option, remote debugging will be enabled. When
178
- this option is enabled, you can insert `page.driver.debug` into your tests to
179
- pause the test and launch a browser which gives you the Chrome inspector to view
180
- all your open pages and inspect them.
181
-
182
- You could set the inspector option via an environment variable:
183
-
184
- ```ruby
185
- Capybara.register_driver :cuprite do |app|
186
- Capybara::Cuprite::Driver.new(app, inspector: ENV['INSPECTOR'])
187
- end
188
- ```
189
-
190
- ## License ##
191
-
192
- Copyright 2018-2019 Machinio
193
-
194
- Permission is hereby granted, free of charge, to any person obtaining
195
- a copy of this software and associated documentation files (the
196
- "Software"), to deal in the Software without restriction, including
197
- without limitation the rights to use, copy, modify, merge, publish,
198
- distribute, sublicense, and/or sell copies of the Software, and to
199
- permit persons to whom the Software is furnished to do so, subject to
200
- the following conditions:
201
-
202
- The above copyright notice and this permission notice shall be
203
- included in all copies or substantial portions of the Software.
204
-
205
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
206
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
207
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
208
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
209
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
210
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
211
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -2,18 +2,12 @@
2
2
 
3
3
  require "ferrum"
4
4
  require "capybara"
5
-
6
- module Capybara::Cuprite
7
- end
8
-
9
5
  require "capybara/cuprite/driver"
10
6
  require "capybara/cuprite/browser"
11
7
  require "capybara/cuprite/page"
12
8
  require "capybara/cuprite/node"
13
9
  require "capybara/cuprite/errors"
14
10
 
15
- Ferrum::Page.prepend(Capybara::Cuprite::Page)
16
-
17
11
  Capybara.register_driver(:cuprite) do |app|
18
12
  Capybara::Cuprite::Driver.new(app)
19
13
  end
@@ -6,7 +6,6 @@ module Capybara::Cuprite
6
6
  class Browser < Ferrum::Browser
7
7
  extend Forwardable
8
8
 
9
- delegate %i[find_or_create_page] => :targets
10
9
  delegate %i[send_keys select set hover trigger before_click switch_to_frame
11
10
  find_modal accept_confirm dismiss_confirm accept_prompt
12
11
  dismiss_prompt reset_modals] => :page
@@ -19,6 +18,22 @@ module Capybara::Cuprite
19
18
  self.url_whitelist = options[:url_whitelist]
20
19
 
21
20
  super
21
+ @page = false
22
+ end
23
+
24
+ def page
25
+ raise Ferrum::NoSuchPageError if @page.nil?
26
+ @page ||= attach_page
27
+ end
28
+
29
+ def reset
30
+ super
31
+ @page = attach_page
32
+ end
33
+
34
+ def quit
35
+ super
36
+ @page = false
22
37
  end
23
38
 
24
39
  def url_whitelist=(patterns)
@@ -53,16 +68,46 @@ module Capybara::Cuprite
53
68
  find_all(method, selector, { "objectId" => object_id })
54
69
  end
55
70
 
71
+ def window_handle
72
+ page.target_id
73
+ end
74
+
75
+ def window_handles
76
+ targets.keys
77
+ end
78
+
56
79
  def within_window(locator = nil, &block)
80
+ original = window_handle
81
+
57
82
  if Capybara::VERSION.to_f < 3.0
58
83
  target_id = window_handles.find do |target_id|
59
- page = find_or_create_page(target_id)
84
+ page = attach_page(target_id)
60
85
  locator == page.frame_name
61
86
  end
62
87
  locator = target_id if target_id
63
88
  end
64
89
 
65
- targets.within_window(locator, &block)
90
+ if window_handles.include?(locator)
91
+ switch_to_window(locator)
92
+ yield
93
+ else
94
+ raise Ferrum::NoSuchPageError
95
+ end
96
+ ensure
97
+ switch_to_window(original)
98
+ end
99
+
100
+ def switch_to_window(target_id)
101
+ target = targets[target_id]
102
+ raise Ferrum::NoSuchPageError unless target
103
+ @page = attach_page(target.id)
104
+ end
105
+
106
+ def close_window(target_id)
107
+ target = targets[target_id]
108
+ raise Ferrum::NoSuchPageError unless target
109
+ @page = nil if @page.target_id == target.id
110
+ target.page.close
66
111
  end
67
112
 
68
113
  def browser_error
@@ -155,5 +200,15 @@ module Capybara::Cuprite
155
200
  end
156
201
  end
157
202
  end
203
+
204
+ def attach_page(target_id = nil)
205
+ target = targets[target_id] if target_id
206
+ target ||= default_context.default_target
207
+ return target.page if target.attached?
208
+
209
+ target.maybe_sleep_if_new_window
210
+ target.page = Page.new(target.id, self)
211
+ target.page
212
+ end
158
213
  end
159
214
  end
@@ -26,6 +26,8 @@ module Capybara::Cuprite
26
26
  @screen_size ||= DEFAULT_MAXIMIZE_SCREEN_SIZE
27
27
 
28
28
  @options[:save_path] = Capybara.save_path.to_s if Capybara.save_path
29
+
30
+ ENV["FERRUM_DEBUG"] = "true" if ENV["CUPRITE_DEBUG"]
29
31
  end
30
32
 
31
33
  def needs_server?
@@ -50,7 +52,7 @@ module Capybara::Cuprite
50
52
  end
51
53
 
52
54
  def frame_url
53
- browser.frame_url
55
+ evaluate_script("window.location.href")
54
56
  end
55
57
 
56
58
  def html
@@ -71,7 +73,7 @@ module Capybara::Cuprite
71
73
  end
72
74
 
73
75
  def frame_title
74
- browser.frame_title
76
+ evaluate_script("document.title")
75
77
  end
76
78
 
77
79
  def find_xpath(selector)
@@ -129,7 +131,10 @@ module Capybara::Cuprite
129
131
  end
130
132
 
131
133
  def open_new_window
132
- browser.open_new_window
134
+ target = browser.default_context.create_target
135
+ target.maybe_sleep_if_new_window
136
+ target.page = Page.new(target.id, browser)
137
+ target.page
133
138
  end
134
139
 
135
140
  def switch_to_window(handle)
@@ -141,7 +146,7 @@ module Capybara::Cuprite
141
146
  end
142
147
 
143
148
  def no_such_window_error
144
- Ferrum::NoSuchWindowError
149
+ Ferrum::NoSuchPageError
145
150
  end
146
151
 
147
152
  def reset!
@@ -238,7 +243,9 @@ module Capybara::Cuprite
238
243
  server = type ? "#{type}=#{ip}:#{port}" : "#{ip}:#{port}"
239
244
  @options[:browser_options].merge!("proxy-server" => server)
240
245
  @options[:browser_options].merge!("proxy-bypass-list" => bypass) if bypass
241
- browser.network.authorize(type: :proxy, user: user, password: password)
246
+ browser.network.authorize(type: :proxy, user: user, password: password) do |request|
247
+ request.continue
248
+ end
242
249
  end
243
250
 
244
251
  def headers
@@ -282,22 +289,41 @@ module Capybara::Cuprite
282
289
  browser.cookies.clear
283
290
  end
284
291
 
292
+ def wait_for_network_idle(**options)
293
+ browser.network.wait_for_idle(**options)
294
+ end
295
+
285
296
  def clear_memory_cache
286
297
  browser.network.clear(:cache)
287
298
  end
288
299
 
289
300
  def basic_authorize(user, password)
290
- browser.network.authorize(user: user, password: password)
301
+ browser.network.authorize(user: user, password: password) do |request|
302
+ request.continue
303
+ end
291
304
  end
292
305
  alias_method :authorize, :basic_authorize
293
306
 
294
- def debug
307
+ def debug_url
308
+ "http://#{browser.process.host}:#{browser.process.port}"
309
+ end
310
+
311
+ def debug(binding = nil)
295
312
  if @options[:inspector]
296
- Process.spawn(browser.process.path, "http://#{browser.process.host}:#{browser.process.port}")
297
- pause
313
+ Process.spawn(browser.process.path, debug_url)
314
+
315
+ if binding&.respond_to?(:pry)
316
+ Pry.start(binding)
317
+ elsif binding&.respond_to?(:irb)
318
+ binding.irb
319
+ else
320
+ pause
321
+ end
298
322
  else
299
323
  raise Error, "To use the remote debugging, you have to launch " \
300
- "the driver with `inspector: true` configuration option"
324
+ "the driver with `inspector: ENV['INSPECTOR']` " \
325
+ "configuration option and run your test suite passing " \
326
+ "env variable"
301
327
  end
302
328
  end
303
329
 
@@ -308,7 +334,7 @@ module Capybara::Cuprite
308
334
  # In jRuby - STDIN returns immediately from select
309
335
  # see https://github.com/jruby/jruby/issues/1783
310
336
  read, write = IO.pipe
311
- Thread.new { IO.copy_stream(STDIN, write); write.close }
337
+ thread = Thread.new { IO.copy_stream(STDIN, write); write.close }
312
338
 
313
339
  STDERR.puts "Cuprite execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue."
314
340
 
@@ -324,6 +350,8 @@ module Capybara::Cuprite
324
350
  end
325
351
  end
326
352
  ensure
353
+ thread.kill
354
+ read.close
327
355
  trap("SIGCONT", old_trap) # Restore the previous signal handler, if there was one.
328
356
  STDERR.puts "Continuing"
329
357
  end
@@ -335,7 +363,8 @@ module Capybara::Cuprite
335
363
  def invalid_element_errors
336
364
  [Capybara::Cuprite::ObsoleteNode,
337
365
  Capybara::Cuprite::MouseEventFailed,
338
- Ferrum::NoExecutionContextError]
366
+ Ferrum::NoExecutionContextError,
367
+ Ferrum::NodeNotFoundError]
339
368
  end
340
369
 
341
370
  def go_back
@@ -350,6 +379,10 @@ module Capybara::Cuprite
350
379
  browser.refresh
351
380
  end
352
381
 
382
+ def wait_for_reload(*args)
383
+ browser.wait_for_reload(*args)
384
+ end
385
+
353
386
  def accept_modal(type, options = {})
354
387
  case type
355
388
  when :alert, :confirm
@@ -128,7 +128,7 @@ class Cuprite {
128
128
  this.trigger(node, "focus");
129
129
  this.setValue(node, "");
130
130
 
131
- if (node.type == "number" || node.type == "date") {
131
+ if (node.type == "number" || node.type == "date" || node.type == "range") {
132
132
  this.setValue(node, value);
133
133
  this.input(node);
134
134
  } else if (node.type == "time") {
@@ -104,6 +104,8 @@ module Capybara::Cuprite
104
104
  when "file"
105
105
  files = value.respond_to?(:to_ary) ? value.to_ary.map(&:to_s) : value.to_s
106
106
  command(:select_file, files)
107
+ when "color"
108
+ node.evaluate("this.setAttribute('value', '#{value}')")
107
109
  else
108
110
  command(:set, value.to_s)
109
111
  end
@@ -144,16 +146,16 @@ module Capybara::Cuprite
144
146
  command(:disabled?)
145
147
  end
146
148
 
147
- def click(keys = [], offset = {})
148
- prepare_and_click(:left, __method__, keys, offset)
149
+ def click(keys = [], **options)
150
+ prepare_and_click(:left, __method__, keys, options)
149
151
  end
150
152
 
151
- def right_click(keys = [], offset = {})
152
- prepare_and_click(:right, __method__, keys, offset)
153
+ def right_click(keys = [], **options)
154
+ prepare_and_click(:right, __method__, keys, options)
153
155
  end
154
156
 
155
- def double_click(keys = [], offset = {})
156
- prepare_and_click(:double, __method__, keys, offset)
157
+ def double_click(keys = [], **options)
158
+ prepare_and_click(:double, __method__, keys, options)
157
159
  end
158
160
 
159
161
  def hover
@@ -225,9 +227,12 @@ module Capybara::Cuprite
225
227
 
226
228
  private
227
229
 
228
- def prepare_and_click(mode, name, keys, offset)
230
+ def prepare_and_click(mode, name, keys, options)
231
+ delay = options[:delay].to_i
232
+ x, y = options.values_at(:x, :y)
233
+ offset = { x: x, y: y, position: options[:offset] || :top }
229
234
  command(:before_click, name, keys, offset)
230
- node.click(mode: mode, keys: keys, offset: offset)
235
+ node.click(mode: mode, keys: keys, offset: offset, delay: delay)
231
236
  end
232
237
 
233
238
  def filter_text(text)
@@ -1,13 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
4
+
3
5
  module Capybara::Cuprite
4
- module Page
6
+ class Page < Ferrum::Page
5
7
  MODAL_WAIT = ENV.fetch("CUPRITE_MODAL_WAIT", 0.05).to_f
8
+ TRIGGER_CLICK_WAIT = ENV.fetch("CUPRITE_TRIGGER_CLICK_WAIT", 0.1).to_f
9
+
10
+ extend Forwardable
11
+ delegate %i[at_css at_xpath css xpath
12
+ current_url current_title body execution_id
13
+ evaluate evaluate_on evaluate_async execute] => :active_frame
6
14
 
7
15
  def initialize(*args)
8
- super
16
+ @frame_stack = []
9
17
  @accept_modal = []
10
18
  @modal_messages = []
19
+ super
11
20
  end
12
21
 
13
22
  def set(node, value)
@@ -21,7 +30,7 @@ module Capybara::Cuprite
21
30
 
22
31
  def trigger(node, event)
23
32
  options = {}
24
- options.merge!(wait: Ferrum::Mouse::CLICK_WAIT) if event.to_s == "click"
33
+ options.merge!(wait: TRIGGER_CLICK_WAIT) if event.to_s == "click" && TRIGGER_CLICK_WAIT > 0
25
34
  evaluate_on(node: node, expression: %(_cuprite.trigger(this, "#{event}")), **options)
26
35
  end
27
36
 
@@ -59,7 +68,7 @@ module Capybara::Cuprite
59
68
 
60
69
  def find_modal(options)
61
70
  start = Ferrum.monotonic_time
62
- timeout = options.fetch(:wait) { session_wait_time }
71
+ timeout = options.fetch(:wait, browser.timeout)
63
72
  expect_text = options[:text]
64
73
  expect_regexp = expect_text.is_a?(Regexp) ? expect_text : Regexp.escape(expect_text.to_s)
65
74
  not_found_msg = "Unable to find modal dialog"
@@ -85,7 +94,12 @@ module Capybara::Cuprite
85
94
 
86
95
  def before_click(node, name, keys = [], offset = {})
87
96
  evaluate_on(node: node, expression: "_cuprite.scrollIntoViewport(this)")
88
- x, y = find_position(node, offset[:x], offset[:y])
97
+
98
+ # If offset is given it may go outside of the element and likely error
99
+ # will be raised that we detected another element at this position.
100
+ return true if offset[:x] || offset[:y]
101
+
102
+ x, y = find_position(node, **offset)
89
103
  evaluate_on(node: node, expression: "_cuprite.mouseEventTest(this, '#{name}', #{x}, #{y})")
90
104
  true
91
105
  rescue Ferrum::JavaScriptError => e
@@ -104,6 +118,14 @@ module Capybara::Cuprite
104
118
  end
105
119
  end
106
120
 
121
+ def frame_name
122
+ evaluate("window.name")
123
+ end
124
+
125
+ def title
126
+ active_frame.current_title
127
+ end
128
+
107
129
  private
108
130
 
109
131
  def prepare_page
@@ -115,15 +137,15 @@ module Capybara::Cuprite
115
137
  on(:request) do |request, index, total|
116
138
  if @browser.url_blacklist && !@browser.url_blacklist.empty?
117
139
  if @browser.url_blacklist.any? { |r| request.match?(r) }
118
- request.abort and return
140
+ request.abort and next
119
141
  else
120
- request.continue and return
142
+ request.continue and next
121
143
  end
122
144
  elsif @browser.url_whitelist && !@browser.url_whitelist.empty?
123
145
  if @browser.url_whitelist.any? { |r| request.match?(r) }
124
- request.continue and return
146
+ request.continue and next
125
147
  else
126
- request.abort and return
148
+ request.abort and next
127
149
  end
128
150
  elsif index + 1 < total
129
151
  # There are other callbacks that may handle this request
@@ -144,7 +166,11 @@ module Capybara::Cuprite
144
166
  options.merge!(promptText: response) if response
145
167
  command("Page.handleJavaScriptDialog", **options)
146
168
  else
147
- 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"
169
+ with_text = params["message"] ? "with text `#{params["message"]}` " : ""
170
+ warn "Modal window #{with_text}has been opened, but you didn't wrap "\
171
+ "your code into (`accept_prompt` | `dismiss_prompt` | "\
172
+ "`accept_confirm` | `dismiss_confirm` | `accept_alert`), "\
173
+ "accepting by default"
148
174
  options = { accept: true }
149
175
  response = params["defaultPrompt"]
150
176
  options.merge!(promptText: response) if response
@@ -153,8 +179,8 @@ module Capybara::Cuprite
153
179
  end
154
180
  end
155
181
 
156
- def find_position(node, *args)
157
- x, y = node.find_position(*args)
182
+ def find_position(node, **options)
183
+ x, y = node.find_position(**options)
158
184
  rescue Ferrum::BrowserError => e
159
185
  if e.message == "Could not compute content quads."
160
186
  raise MouseEventFailed.new("MouseEventFailed: click, none, 0, 0")
@@ -162,5 +188,13 @@ module Capybara::Cuprite
162
188
  raise
163
189
  end
164
190
  end
191
+
192
+ def active_frame
193
+ if @frame_stack.empty?
194
+ main_frame
195
+ else
196
+ @frames[@frame_stack.last]
197
+ end
198
+ end
165
199
  end
166
200
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Cuprite
5
- VERSION = "0.7.1"
5
+ VERSION = "0.12"
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.7.1
4
+ version: '0.12'
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-09-20 00:00:00.000000000 Z
11
+ date: 2021-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara
@@ -34,16 +34,16 @@ dependencies:
34
34
  name: ferrum
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ">="
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: 0.2.1
39
+ version: 0.10.0
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ">="
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.2.1
46
+ version: 0.10.0
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: image_size
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -189,7 +189,7 @@ files:
189
189
  - lib/capybara/cuprite/node.rb
190
190
  - lib/capybara/cuprite/page.rb
191
191
  - lib/capybara/cuprite/version.rb
192
- homepage: https://github.com/machinio/cuprite
192
+ homepage: https://github.com/rubycdp/cuprite
193
193
  licenses:
194
194
  - MIT
195
195
  metadata: {}
@@ -208,7 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
208
208
  - !ruby/object:Gem::Version
209
209
  version: '0'
210
210
  requirements: []
211
- rubygems_version: 3.0.3
211
+ rubygems_version: 3.1.2
212
212
  signing_key:
213
213
  specification_version: 4
214
214
  summary: Headless Chrome driver for Capybara