cuprite 0.19 → 0.20

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: 7fa19bf03d8a264421374bf813afb0f6c062e15e8aa92ca7c046d3a659122319
4
- data.tar.gz: d12031c15dda9ad7e777f7b66a887c6979af5b4c3e27010083378af512f979d1
3
+ metadata.gz: 159c7e6289accaf81577a8549e3e1447ea90e0f99694b91547704fef206b8c56
4
+ data.tar.gz: 1b70f9e72fbdd0a089d45229ecb12604d655a782ca2a10b358220b66865e5d09
5
5
  SHA512:
6
- metadata.gz: c85ee6afd497cfdc03375c111cc58dbcd6463f41c63bd745a6229a31a4d4e6bb6dc03fb8eddc0f8a7f93079c2cdf81524a3febca3494031e002eb869e9943188
7
- data.tar.gz: 0d066fa1a7fb851a9ce0d3d9a9e4891a6c76bd3c7e558a74fa021035b8e2316d860fce4674f3054134fe03dc05c7c622124d8b88ded27d81bfeede3d75e28e7f
6
+ metadata.gz: dac1cb17193f518ce4e276c2580d02118e9a1cce86737c8e0b501c2747a438b89dfda2ed2eb5b66b23e4a92fa7d30c53cbb991ae172358fbbb12531fd441a777
7
+ data.tar.gz: f9758427a804d2dc3ecda09610598fa2a22389f520789800fc5b71bf60f5cef9be01a48a464610a1200914ab4f005ca71634557f9b113586c594097a0c592884
data/README.md CHANGED
@@ -58,7 +58,8 @@ because Docker is involved. Prefer running the browser as a non-root user with
58
58
  a working sandbox when possible.
59
59
 
60
60
  Since Cuprite uses [Ferrum](https://github.com/rubycdp/ferrum#examples) there
61
- are many useful methods you can call even using this driver:
61
+ are many useful methods you can call even using this driver.
62
+ `page.driver.browser` is a `Ferrum::Browser`:
62
63
 
63
64
  ```ruby
64
65
  browser = page.driver.browser
@@ -129,35 +130,35 @@ opened where you can further experiment with the test.
129
130
 
130
131
  ## Clicking/Scrolling
131
132
 
132
- * `page.driver.click(x, y)` Click a very specific area of the screen.
133
- * `page.driver.scroll_to(left, top)` Scroll to a given position.
133
+ * `page.driver.browser.mouse.click(x:, y:)` Click a very specific area of the screen.
134
+ * `page.scroll_to(left, top)` Scroll to a given position.
134
135
  * `element.send_keys(*keys)` Send keys to a given node.
135
136
 
136
137
 
137
138
  ## Request headers
138
139
 
139
- Manipulate HTTP request headers like a boss:
140
+ Manipulate HTTP request headers with Ferrum:
140
141
 
141
142
  ``` ruby
142
- page.driver.headers # => {}
143
- page.driver.headers = { "User-Agent" => "Cuprite" }
144
- page.driver.add_headers("Referer" => "https://example.com")
145
- page.driver.headers # => { "User-Agent" => "Cuprite", "Referer" => "https://example.com" }
143
+ page.driver.browser.headers.get # => {}
144
+ page.driver.browser.headers.set("User-Agent" => "Cuprite")
145
+ page.driver.browser.headers.add({ "Referer" => "https://example.com" })
146
+ page.driver.browser.headers.get # => { "User-Agent" => "Cuprite", "Referer" => "https://example.com" }
146
147
  ```
147
148
 
148
- Notice that `headers=` will overwrite already set headers. You should use
149
- `add_headers` if you want to add a few more. These headers will apply to all
150
- subsequent HTTP requests (including requests for assets, AJAX, etc). They will
151
- be automatically cleared at the end of the test.
149
+ Notice that `set` will overwrite already set headers. You should use `add` if
150
+ you want to add a few more. These headers will apply to all subsequent HTTP
151
+ requests (including requests for assets, AJAX, etc). They will be automatically
152
+ cleared at the end of the test.
152
153
 
153
154
 
154
155
  ## Network traffic
155
156
 
156
- * `page.driver.network_traffic` allows you to inspect network traffic (i.e., loaded resources) on the current page. It returns an array of `Ferrum::Network::Exchange` objects, each representing a network request/response exchange. You can query both the request and response details of each exchange.
157
+ * `page.driver.browser.network.traffic` allows you to inspect network traffic (i.e., loaded resources) on the current page. It returns an array of `Ferrum::Network::Exchange` objects, each representing a network request/response exchange. You can query both the request and response details of each exchange.
157
158
 
158
159
  ```ruby
159
160
  # Retrieve all network exchanges
160
- network_traffic = page.driver.network_traffic
161
+ network_traffic = page.driver.browser.network.traffic
161
162
 
162
163
  # Access the first exchange
163
164
  first_exchange = network_traffic.first
@@ -166,22 +167,22 @@ first_exchange = network_traffic.first
166
167
  response = first_exchange.response
167
168
  ```
168
169
 
169
- * `page.driver.wait_for_network_idle` Natively waits for network idle and if
170
- there are no active connections returns or raises `TimeoutError` error. Accepts
171
- the same options as
170
+ * `page.driver.browser.network.wait_for_idle` Natively waits for network idle
171
+ and returns `false` if there are still active connections after timeout.
172
+ Accepts the same options as
172
173
  [`wait_for_idle`](https://github.com/rubycdp/ferrum#wait_for_idleoptions)
173
174
 
174
175
  ```ruby
175
- page.driver.wait_for_network_idle
176
+ page.driver.browser.network.wait_for_idle
176
177
  page.driver.refresh
177
178
  ```
178
179
 
179
180
  Please note that network traffic is not cleared when you visit new page. You can
180
- manually clear the network traffic by calling `page.driver.clear_network_traffic`
181
- or `page.driver.reset`
181
+ manually clear the network traffic by calling
182
+ `page.driver.browser.network.clear(:traffic)` or `page.driver.reset!`
182
183
 
183
- * `page.driver.wait_for_reload` unlike `wait_for_network_idle` will wait until
184
- the whole page is reloaded or raise a timeout error. It's useful when you know
184
+ * `page.driver.browser.wait_for_reload` unlike `network.wait_for_idle` will wait until
185
+ the whole page is reloaded or the timeout passes. It's useful when you know
185
186
  that for example after clicking autocomplete suggestion you expect page to be
186
187
  reloaded, you have a few choices - put sleep or wait for network idle, but both
187
188
  are bad. Sleep makes you wait longer or less than needed, network idle can
@@ -191,44 +192,45 @@ rescue.
191
192
 
192
193
  ## Manipulating cookies
193
194
 
194
- The following methods are used to inspect and manipulate cookies:
195
+ Use Ferrum's cookies to inspect and manipulate cookies:
195
196
 
196
- * `page.driver.cookies` - a hash of cookies accessible to the current
197
- page. The keys are cookie names. The values are `Cookie` objects, with
198
- the following methods: `name`, `value`, `domain`, `path`, `size`, `secure?`,
199
- `httponly?`, `session?`, `expires`.
200
- * `page.driver.set_cookie(name, value, options = {})` - set a cookie.
201
- The options hash can take the following keys: `:domain`, `:path`,
202
- `:secure`, `:httponly`, `:expires`. `:expires` should be a
203
- `Time` object.
204
- * `page.driver.remove_cookie(name)` - remove a cookie
205
- * `page.driver.clear_cookies` - clear all cookies
197
+ ```ruby
198
+ cookies = page.driver.browser.cookies
199
+ cookies.set(name: "stealth", value: "omg")
200
+ cookies["stealth"].value # => "omg"
201
+ cookies.all # => { "stealth" => #<Ferrum::Cookies::Cookie ...> }
202
+ cookies.remove(name: "stealth")
203
+ cookies.clear
204
+ ```
206
205
 
207
206
 
208
207
  ## Screenshot
209
208
 
210
- Besides capybara screenshot method you can get image as Base64:
209
+ Besides capybara screenshot method you can get image or PDF as Base64 with Ferrum:
211
210
 
212
- * `page.driver.render_base64(format, options)`
211
+ ```ruby
212
+ page.driver.browser.screenshot(format: :jpeg, full: true)
213
+ page.driver.browser.pdf(paper_width: 6, paper_height: 12)
214
+ ```
213
215
 
214
216
 
215
217
  ## Authorization
216
218
 
217
- * `page.driver.basic_authorize(user, password)`
219
+ * `page.driver.browser.network.authorize(user:, password:) { |request, _index, _total| request.continue }`
218
220
 
219
221
  ## Proxy
220
222
 
221
- * `page.driver.set_proxy(ip, port, user, password, bypass)`
223
+ ```ruby
224
+ Capybara::Cuprite::Driver.new(app, proxy: { host: "127.0.0.1", port: 8080, user: "user", password: "password" })
225
+ ```
222
226
 
223
- The proxy is passed to Chrome as a command-line flag, so it has to be set before the browser starts: either with the
224
- `:proxy` driver option or by calling `set_proxy` before the browser is first used. Calling it once the browser is
225
- running has no effect.
227
+ The proxy is passed to Chrome as a command-line flag when the browser starts.
226
228
 
227
229
  When connecting to a remote browser with the `:url` option (browserless, Selenoid, etc.), Cuprite doesn't launch
228
230
  Chrome and the proxy flag is never applied, so configure the proxy on the remote browser itself. The `user` and
229
231
  `password` are still used to answer the proxy's authentication challenge.
230
232
 
231
- The `bypass` argument is a list of hosts that skip the proxy, so don't pass the host you want to go through it.
233
+ The `bypass` option is a list of hosts that skip the proxy, so don't pass the host you want to go through it.
232
234
 
233
235
 
234
236
  ## URL Blocklisting & Allowlisting
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "capybara/cuprite/deprecation"
4
+
5
+ module Capybara
6
+ module Cuprite
7
+ class Browser < Ferrum::Browser
8
+ module Deprecations
9
+ def visit(*args)
10
+ Cuprite.deprecation_warning("#{self.class}#visit", "use #goto")
11
+ goto(*args)
12
+ end
13
+
14
+ def status_code
15
+ Cuprite.deprecation_warning("#{self.class}#status_code", "use #network.status")
16
+ network.status
17
+ end
18
+
19
+ def window_handle
20
+ Cuprite.deprecation_warning("#{self.class}#window_handle", "use #current_window_handle")
21
+ current_window_handle
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Cuprite
5
+ class Browser < Ferrum::Browser
6
+ module Extensions
7
+ def raise_on_unhandled_modal
8
+ @options.raise_on_unhandled_modal
9
+ end
10
+
11
+ def raise_on_unhandled_modal=(value)
12
+ @options.raise_on_unhandled_modal = value
13
+ end
14
+
15
+ def url_whitelist
16
+ @options.url_whitelist
17
+ end
18
+ alias url_allowlist url_whitelist
19
+
20
+ def url_whitelist=(patterns)
21
+ @options.url_whitelist = prepare_wildcards(patterns)
22
+ page.network.whitelist = @options.url_whitelist if @client && @options.url_whitelist.any?
23
+ end
24
+ alias url_allowlist= url_whitelist=
25
+
26
+ def url_blacklist
27
+ @options.url_blacklist
28
+ end
29
+ alias url_blocklist url_blacklist
30
+
31
+ def url_blacklist=(patterns)
32
+ @options.url_blacklist = prepare_wildcards(patterns)
33
+ page.network.blacklist = @options.url_blacklist if @client && @options.url_blacklist.any?
34
+ end
35
+ alias url_blocklist= url_blacklist=
36
+
37
+ private
38
+
39
+ def prepare_wildcards(patterns)
40
+ string_passed = false
41
+
42
+ Array(patterns).map do |pattern|
43
+ if pattern.is_a?(Regexp)
44
+ pattern
45
+ else
46
+ string_passed = true
47
+ pattern = pattern.gsub("*", ".*")
48
+ Regexp.new(pattern, Regexp::IGNORECASE)
49
+ end
50
+ end
51
+ ensure
52
+ if string_passed
53
+ warn "Passing strings to blacklist/whitelist is deprecated, pass regexp at #{caller(4..4).first}"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "forwardable"
3
+ require "capybara/cuprite/browser/deprecations"
4
+ require "capybara/cuprite/browser/extensions"
4
5
 
5
6
  module Capybara
6
7
  module Cuprite
7
8
  class Browser < Ferrum::Browser
8
- extend Forwardable
9
-
10
- DRAG_HTML5_JS = File.read(File.expand_path("javascripts/drag.js", __dir__))
11
-
12
- delegate %i[send_keys select set hover trigger before_click switch_to_frame
13
- find_modal accept_confirm dismiss_confirm accept_prompt
14
- dismiss_prompt reset_modals] => :page
9
+ include Deprecations
10
+ include Extensions
15
11
 
16
12
  def initialize(options = nil)
17
13
  device = options&.dig(:emulate)
@@ -33,7 +29,7 @@ module Capybara
33
29
  end
34
30
 
35
31
  def page
36
- raise Ferrum::NoSuchPageError if @page&.closed?
32
+ raise Ferrum::NoSuchPageError if @page_closed
37
33
 
38
34
  @page ||= attach_page
39
35
  end
@@ -42,12 +38,12 @@ module Capybara
42
38
  super
43
39
  ensure
44
40
  @options.reset_window_size
45
- @page = nil
41
+ forget_page
46
42
  end
47
43
 
48
44
  def quit
49
45
  super
50
- @page = nil
46
+ forget_page
51
47
  end
52
48
 
53
49
  def resize(**options)
@@ -55,59 +51,7 @@ module Capybara
55
51
  super
56
52
  end
57
53
 
58
- def raise_on_unhandled_modal
59
- @options.raise_on_unhandled_modal
60
- end
61
-
62
- def raise_on_unhandled_modal=(value)
63
- @options.raise_on_unhandled_modal = value
64
- end
65
-
66
- def url_whitelist
67
- @options.url_whitelist
68
- end
69
- alias url_allowlist url_whitelist
70
-
71
- def url_whitelist=(patterns)
72
- @options.url_whitelist = prepare_wildcards(patterns)
73
- page.network.whitelist = @options.url_whitelist if @client && @options.url_whitelist.any?
74
- end
75
- alias url_allowlist= url_whitelist=
76
-
77
- def url_blacklist
78
- @options.url_blacklist
79
- end
80
- alias url_blocklist url_blacklist
81
-
82
- def url_blacklist=(patterns)
83
- @options.url_blacklist = prepare_wildcards(patterns)
84
- page.network.blacklist = @options.url_blacklist if @client && @options.url_blacklist.any?
85
- end
86
- alias url_blocklist= url_blacklist=
87
-
88
- def visit(*args)
89
- goto(*args)
90
- end
91
-
92
- def status_code
93
- network.status
94
- end
95
-
96
- def find(method, selector)
97
- find_all(method, selector)
98
- end
99
-
100
- def property(node, name)
101
- node.property(name)
102
- end
103
-
104
- def find_within(node, method, selector)
105
- resolved = page.command("DOM.resolveNode", nodeId: node.node_id)
106
- object_id = resolved.dig("object", "objectId")
107
- find_all(method, selector, { "objectId" => object_id })
108
- end
109
-
110
- def window_handle
54
+ def current_window_handle
111
55
  page.target_id
112
56
  end
113
57
 
@@ -115,190 +59,32 @@ module Capybara
115
59
  targets.values.select(&:page?).map(&:id)
116
60
  end
117
61
 
118
- def within_window(locator = nil)
119
- original = window_handle
120
- raise Ferrum::NoSuchPageError unless window_handles.include?(locator)
121
-
122
- switch_to_window(locator)
123
- yield
124
- ensure
125
- switch_to_window(original)
62
+ def open_new_window
63
+ attach_page(default_context.create_target.id)
126
64
  end
127
65
 
128
66
  def switch_to_window(target_id)
129
67
  target = window_target(target_id)
68
+ forget_page
130
69
  @page = attach_page(target.id)
131
70
  @page.activate
132
71
  end
133
72
 
134
73
  def close_window(target_id)
135
74
  target = window_target(target_id)
136
- @page = ClosedPage.new if @page.target_id == target.id
75
+ if @page&.target_id == target.id
76
+ forget_page
77
+ @page_closed = true
78
+ end
137
79
  target.page.close
138
80
  targets.delete(target_id) # page.close is async, delete target asap
139
81
  end
140
82
 
141
- def active_element
142
- evaluate("document.activeElement")
143
- end
144
-
145
- def browser_error
146
- evaluate("_cuprite.browserError()")
147
- end
148
-
149
- def source
150
- raise NotImplementedError
151
- end
152
-
153
- def drag(node, other, drop_modifiers, options = {})
154
- steps = options.fetch(:steps, 1)
155
- delay = options[:delay]
156
- scroll = options.fetch(:scroll, true)
157
- html5 = options[:html5]
158
- drop_offset = options[:drop_offset]
159
-
160
- execute("_cuprite.dragMousedownTracker()")
161
-
162
- node.scroll_into_view if scroll
163
- x1, y1 = node.find_position
164
-
165
- mouse.move(x: x1, y: y1)
166
- mouse.down
167
- sleep delay if delay
168
-
169
- html5 = !evaluate_on(node: node, expression: "_cuprite.legacyDragCheck(this)") if html5.nil?
170
-
171
- if html5
172
- drag_html5(node, other, drop_modifiers, delay, drop_offset)
173
- else
174
- modifiers = keyboard.modifiers(drop_modifiers)
175
-
176
- other.scroll_into_view if scroll
177
- x2, y2 = other.find_position(**drop_offset.to_h)
178
- mouse.move(x: x2, y: y2, steps: steps)
179
-
180
- mouse.up(modifiers: modifiers)
181
- end
182
- end
183
-
184
- def drag_html5(node, other, drop_modifiers, delay, drop_offset = nil)
185
- keys = drop_modifiers.map(&:to_s)
186
- evaluate_async(DRAG_HTML5_JS, timeout, node, other, (delay || 0.05) * 1000, keys, drop_offset)
187
- mouse.up
188
- end
189
-
190
- def drag_by(node, dx, dy, steps, delay = nil, scroll = true)
191
- x1, y1 = node.find_position
192
-
193
- mouse.move(x: x1, y: y1)
194
- mouse.down
195
-
196
- sleep delay if delay
197
-
198
- evaluate("window.scrollBy(#{dx}, #{dy})") if scroll # should be extracted to Mouse#scroll_by in ferrum
199
-
200
- x2, y2 = node.find_position
201
- mouse.move(x: x2 + dx, y: y2 + dy, steps: steps)
202
- mouse.up
203
- end
204
-
205
- def drop(node, *args)
206
- if args[0].is_a?(String)
207
- execute("_cuprite.attachDropInput()")
208
- input = find(:css, "#_cuprite_drop_file").first
209
- select_file(input, args)
210
- evaluate_on(node: node, expression: "_cuprite.dropFile(this)")
211
- else
212
- strings = args.flat_map { |arg| arg.map { |type, data| { "type" => type, "data" => data } } }
213
- evaluate_on(node: node, expression: "_cuprite.dropString(#{strings.to_json}, this)")
214
- end
215
- end
216
-
217
- def select_file(node, value)
218
- node.select_file(value)
219
- end
220
-
221
- def parents(node)
222
- evaluate_on(node: node, expression: "_cuprite.parents(this)", by_value: false)
223
- end
224
-
225
- def visible_text(node)
226
- evaluate_on(node: node, expression: "_cuprite.visibleText(this)")
227
- end
228
-
229
- def delete_text(node)
230
- evaluate_on(node: node, expression: "_cuprite.deleteText(this)")
231
- end
232
-
233
- def attributes(node)
234
- value = evaluate_on(node: node, expression: "_cuprite.getAttributes(this)")
235
- JSON.parse(value)
236
- end
237
-
238
- def attribute(node, name)
239
- evaluate_on(node: node, expression: %(_cuprite.getAttribute(this, "#{name}")))
240
- end
241
-
242
- def value(node)
243
- evaluate_on(node: node, expression: "_cuprite.value(this)")
244
- end
245
-
246
- def visible?(node)
247
- evaluate_on(node: node, expression: "_cuprite.isVisible(this)")
248
- end
249
-
250
- def disabled?(node)
251
- evaluate_on(node: node, expression: "_cuprite.isDisabled(this)")
252
- end
253
-
254
- def path(node)
255
- evaluate_on(node: node, expression: "_cuprite.path(this)")
256
- end
257
-
258
- def obscured?(node)
259
- evaluate_on(node: node, expression: "_cuprite.isObscured(this)")
260
- end
261
-
262
- def all_text(node)
263
- node.text
264
- end
265
-
266
83
  private
267
84
 
268
- def find_all(method, selector, within = nil)
269
- # Capybara appends its text-match hint as `(expr)[...]`, but with `exact_text: true` the hint
270
- # is empty and the result `(expr)[]` is invalid XPath. An empty predicate is never valid, so
271
- # dropping it can't change a valid selector. Remove once teamcapybara/capybara#2860 is released.
272
- selector = selector.delete_suffix("[]") if method == :xpath
273
- nodes = if within
274
- evaluate("_cuprite.find(arguments[0], arguments[1], arguments[2])", method, selector, within)
275
- else
276
- evaluate("_cuprite.find(arguments[0], arguments[1])", method, selector)
277
- end
278
-
279
- nodes.select(&:node?)
280
- rescue Ferrum::JavaScriptError => e
281
- raise InvalidSelector.new(e.response, method, selector) if e.class_name == "InvalidSelector"
282
-
283
- raise
284
- end
285
-
286
- def prepare_wildcards(patterns)
287
- string_passed = false
288
-
289
- Array(patterns).map do |pattern|
290
- if pattern.is_a?(Regexp)
291
- pattern
292
- else
293
- string_passed = true
294
- pattern = pattern.gsub("*", ".*")
295
- Regexp.new(pattern, Regexp::IGNORECASE)
296
- end
297
- end
298
- ensure
299
- if string_passed
300
- warn "Passing strings to blacklist/whitelist is deprecated, pass regexp at #{caller(4..4).first}"
301
- end
85
+ def forget_page
86
+ @page = nil
87
+ @page_closed = false
302
88
  end
303
89
 
304
90
  def window_target(target_id)
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Cuprite
5
+ REMOVE_DEPRECATED_IN = "0.21"
6
+
7
+ def self.deprecation_warning(method, advice = nil)
8
+ message = "#{method} is deprecated and will be removed in #{REMOVE_DEPRECATED_IN}"
9
+ message += ", #{advice} instead" if advice
10
+ warn message, uplevel: 2
11
+ end
12
+ end
13
+ end