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.
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "capybara/cuprite/deprecation"
5
+
6
+ module Capybara
7
+ module Cuprite
8
+ class Driver < Capybara::Driver::Base
9
+ module Deprecations
10
+ def self.deprecate(name, replacement)
11
+ define_method(name) do |*args, **kwargs, &block|
12
+ Cuprite.deprecation_warning("#{self.class}##{name}", "use ##{replacement}")
13
+ public_send(replacement, *args, **kwargs, &block)
14
+ end
15
+ end
16
+ private_class_method :deprecate
17
+
18
+ deprecate :body, :html
19
+ deprecate :window_handle, :current_window_handle
20
+ deprecate :back, :go_back
21
+ deprecate :forward, :go_forward
22
+ deprecate :render, :save_screenshot
23
+
24
+ def render_base64(format = :png, options = {})
25
+ Cuprite.deprecation_warning("#{self.class}#render_base64", "use page.driver.browser.screenshot or .pdf")
26
+ browser.screenshot(format: format, encoding: :base64, **options)
27
+ end
28
+
29
+ def zoom_factor=(value)
30
+ Cuprite.deprecation_warning("#{self.class}#zoom_factor=", "pass scale: to save_screenshot")
31
+ @zoom_factor = value.to_f
32
+ end
33
+
34
+ def paper_size=(value)
35
+ Cuprite.deprecation_warning("#{self.class}#paper_size=",
36
+ "pass paper_width: and paper_height: to save_screenshot")
37
+ @paper_size = value
38
+ end
39
+
40
+ def resize(width, height)
41
+ Cuprite.deprecation_warning("#{self.class}##{__callee__}", "use page.current_window.resize_to")
42
+ resize_window_to(current_window_handle, width, height)
43
+ end
44
+ alias resize_window resize
45
+
46
+ def click(x, y)
47
+ Cuprite.deprecation_warning("#{self.class}#click", "use page.driver.browser.mouse.click")
48
+ browser.mouse.click(x: x, y: y)
49
+ end
50
+
51
+ def scroll_to(left, top)
52
+ Cuprite.deprecation_warning("#{self.class}#scroll_to", "use page.scroll_to")
53
+ browser.mouse.scroll_to(left, top)
54
+ end
55
+
56
+ def set_proxy(host, port, user = nil, password = nil, bypass = nil)
57
+ Cuprite.deprecation_warning("#{self.class}#set_proxy", "pass the :proxy option to the driver")
58
+ @options.merge!(proxy: { host: host, port: port, user: user, password: password, bypass: bypass })
59
+ end
60
+
61
+ def restart
62
+ Cuprite.deprecation_warning("#{self.class}#restart", "use page.driver.browser.restart")
63
+ browser.restart
64
+ end
65
+
66
+ def wait_for_reload(timeout = 1)
67
+ Cuprite.deprecation_warning("#{self.class}#wait_for_reload", "use page.driver.browser.wait_for_reload")
68
+ browser.wait_for_reload(timeout)
69
+ end
70
+
71
+ def timeout
72
+ Cuprite.deprecation_warning("#{self.class}#timeout", "use page.driver.browser.timeout")
73
+ browser.timeout
74
+ end
75
+
76
+ def timeout=(value)
77
+ Cuprite.deprecation_warning("#{self.class}#timeout=", "use page.driver.browser.timeout=")
78
+ browser.timeout = value
79
+ end
80
+
81
+ def device_pixel_ratio
82
+ Cuprite.deprecation_warning("#{self.class}#device_pixel_ratio", "use page.driver.browser.device_pixel_ratio")
83
+ browser.device_pixel_ratio
84
+ end
85
+
86
+ def viewport_size
87
+ Cuprite.deprecation_warning("#{self.class}#viewport_size", "use page.driver.browser.viewport_size")
88
+ browser.viewport_size
89
+ end
90
+
91
+ def authorize(user, password)
92
+ Cuprite.deprecation_warning("#{self.class}##{__callee__}", "use page.driver.browser.network.authorize")
93
+ browser.network.authorize(user: user, password: password) do |request, _index, _total|
94
+ request.continue
95
+ end
96
+ end
97
+ alias basic_authorize authorize
98
+
99
+ def headers
100
+ Cuprite.deprecation_warning("#{self.class}#headers", "use page.driver.browser.headers.get")
101
+ browser.headers.get
102
+ end
103
+
104
+ def headers=(headers)
105
+ Cuprite.deprecation_warning("#{self.class}#headers=", "use page.driver.browser.headers.set")
106
+ browser.headers.set(headers)
107
+ end
108
+
109
+ def add_headers(headers)
110
+ Cuprite.deprecation_warning("#{self.class}#add_headers", "use page.driver.browser.headers.add")
111
+ browser.headers.add(headers)
112
+ end
113
+
114
+ def add_header(name, value, permanent: true)
115
+ Cuprite.deprecation_warning("#{self.class}#add_header", "use page.driver.browser.headers.add")
116
+ browser.headers.add({ name => value }, permanent: permanent)
117
+ end
118
+
119
+ def cookies
120
+ Cuprite.deprecation_warning("#{self.class}#cookies", "use page.driver.browser.cookies.all")
121
+ browser.cookies.all
122
+ end
123
+
124
+ def set_cookie(name, value, options = {})
125
+ Cuprite.deprecation_warning("#{self.class}#set_cookie", "use page.driver.browser.cookies.set")
126
+ options = options.dup
127
+ options[:name] ||= name
128
+ options[:value] ||= value
129
+ options[:domain] ||= default_domain
130
+ browser.cookies.set(**options)
131
+ end
132
+
133
+ def remove_cookie(name, **options)
134
+ Cuprite.deprecation_warning("#{self.class}#remove_cookie", "use page.driver.browser.cookies.remove")
135
+ options[:domain] = default_domain if options.empty?
136
+ options = options.merge(name: name)
137
+ browser.cookies.remove(**options)
138
+ end
139
+
140
+ def clear_cookies
141
+ Cuprite.deprecation_warning("#{self.class}#clear_cookies", "use page.driver.browser.cookies.clear")
142
+ browser.cookies.clear
143
+ end
144
+
145
+ def network_traffic(type = nil)
146
+ Cuprite.deprecation_warning("#{self.class}#network_traffic", "use page.driver.browser.network.traffic")
147
+ traffic = browser.network.traffic
148
+
149
+ case type.to_s
150
+ when "all"
151
+ traffic
152
+ when "blocked"
153
+ traffic.select(&:blocked?)
154
+ else
155
+ # when request isn't blocked
156
+ traffic.reject(&:blocked?)
157
+ end
158
+ end
159
+
160
+ def clear_network_traffic
161
+ Cuprite.deprecation_warning("#{self.class}#clear_network_traffic",
162
+ "use page.driver.browser.network.clear(:traffic)")
163
+ browser.network.clear(:traffic)
164
+ end
165
+
166
+ def wait_for_network_idle(**options)
167
+ Cuprite.deprecation_warning("#{self.class}#wait_for_network_idle",
168
+ "use page.driver.browser.network.wait_for_idle")
169
+ browser.network.wait_for_idle(**options)
170
+ end
171
+
172
+ def clear_memory_cache
173
+ Cuprite.deprecation_warning("#{self.class}#clear_memory_cache",
174
+ "use page.driver.browser.network.clear(:cache)")
175
+ browser.network.clear(:cache)
176
+ end
177
+
178
+ private
179
+
180
+ def default_domain
181
+ if @started
182
+ URI.parse(browser.current_url).host
183
+ else
184
+ URI.parse(default_cookie_host).host || "127.0.0.1"
185
+ end
186
+ end
187
+
188
+ def default_cookie_host
189
+ if respond_to?(:session_options)
190
+ session_options.app_host
191
+ else
192
+ Capybara.app_host
193
+ end || ""
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Cuprite
5
+ class Driver < Capybara::Driver::Base
6
+ module Extensions
7
+ def debug(...)
8
+ browser.debug(...)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,27 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "uri"
4
3
  require "forwardable"
4
+ require "capybara/cuprite/finder"
5
+ require "capybara/cuprite/driver/deprecations"
6
+ require "capybara/cuprite/driver/extensions"
5
7
 
6
- # rubocop:disable-next Metrics/ClassLength
7
8
  module Capybara
8
9
  module Cuprite
9
10
  class Driver < Capybara::Driver::Base
10
11
  DEFAULT_MAXIMIZE_SCREEN_SIZE = [1366, 768].freeze
11
12
  EXTENSION = File.expand_path("javascripts/index.js", __dir__)
13
+ CALLBACK_JS = <<~JS
14
+ function(...args) {
15
+ return new Promise((resolve, reject) => {
16
+ try {
17
+ (function() {
18
+ SCRIPT
19
+ }).apply(this, [...args, resolve]);
20
+ } catch (error) {
21
+ reject(error);
22
+ }
23
+ });
24
+ }
25
+ JS
12
26
 
13
27
  extend Forwardable
28
+ include Deprecations
29
+ include Extensions
14
30
 
15
- delegate %i[restart quit status_code timeout timeout= current_url title body
16
- window_handles close_window switch_to_window within_window window_handle
17
- back forward refresh wait_for_reload viewport_size device_pixel_ratio] => :browser
31
+ delegate %i[quit current_url title
32
+ window_handles current_window_handle close_window switch_to_window
33
+ refresh] => :browser
18
34
  delegate %i[send_keys] => :active_element
19
- alias html body
20
- alias current_window_handle window_handle
21
- alias go_back back
22
- alias go_forward forward
35
+ def_delegator :browser, :body, :html
36
+ def_delegator :browser, :back, :go_back
37
+ def_delegator :browser, :forward, :go_forward
23
38
 
24
- attr_reader :app, :options, :screen_size
39
+ attr_reader :app, :options
25
40
 
26
41
  def initialize(app, options = {})
27
42
  @app = app
@@ -52,20 +67,21 @@ module Capybara
52
67
  end
53
68
 
54
69
  def browser
55
- @browser ||= Browser.new(@options)
70
+ @browser ||= Browser.new({ base_url: capybara_base_url }.compact.merge(@options))
56
71
  end
57
72
 
58
73
  def visit(url)
59
74
  @started = true
60
- browser.visit(url)
75
+ browser.base_url = url if url.match?(%r{\Ahttps?://})
76
+ browser.goto(url)
61
77
  end
62
78
 
63
- def frame_url
64
- evaluate_script("window.location.href")
79
+ def status_code
80
+ browser.network.status
65
81
  end
66
82
 
67
- def source
68
- browser.source.to_s
83
+ def frame_url
84
+ evaluate_script("window.location.href")
69
85
  end
70
86
 
71
87
  def frame_title
@@ -73,7 +89,7 @@ module Capybara
73
89
  end
74
90
 
75
91
  def active_element
76
- Node.new(self, browser.active_element)
92
+ Node.new(self, browser.evaluate("document.activeElement"))
77
93
  end
78
94
 
79
95
  def find_xpath(selector)
@@ -84,21 +100,13 @@ module Capybara
84
100
  find(:css, selector)
85
101
  end
86
102
 
87
- def find(method, selector)
88
- browser.find(method, selector).map { |native| Node.new(self, native) }
89
- end
90
-
91
- def click(x, y)
92
- browser.mouse.click(x: x, y: y)
93
- end
94
-
95
103
  def evaluate_script(script, *args)
96
104
  result = browser.evaluate(script, *native_args(args))
97
105
  unwrap_script_result(result)
98
106
  end
99
107
 
100
108
  def evaluate_async_script(script, *args)
101
- result = browser.evaluate_async(script, session_wait_time, *native_args(args))
109
+ result = browser.evaluate(CALLBACK_JS.sub("SCRIPT") { script }, *native_args(args), timeout: session_wait_time)
102
110
  unwrap_script_result(result)
103
111
  end
104
112
 
@@ -110,23 +118,20 @@ module Capybara
110
118
  def switch_to_frame(locator)
111
119
  handle = case locator
112
120
  when Capybara::Node::Element
113
- locator.native.description["frameId"]
121
+ locator.native.node.description["frameId"]
114
122
  when Capybara::Cuprite::Node
115
- locator.description["frameId"]
123
+ locator.node.description["frameId"]
116
124
  when :parent, :top
117
125
  locator
118
126
  end
119
127
 
120
128
  raise ArgumentError, "Unable to switch to frame from #{locator.class}" unless handle
121
129
 
122
- browser.switch_to_frame(handle)
130
+ browser.page.switch_to_frame(handle)
123
131
  end
124
132
 
125
133
  def open_new_window
126
- target = browser.default_context.create_target
127
- target.maybe_sleep_if_new_window
128
- target.page = Page.new(target.client, context_id: target.context_id, target_id: target.id)
129
- target.page
134
+ browser.open_new_window
130
135
  end
131
136
 
132
137
  def no_such_window_error
@@ -140,6 +145,8 @@ module Capybara
140
145
  browser.url_whitelist = @options[:url_whitelist]
141
146
  browser.raise_on_unhandled_modal = @options.fetch(:raise_on_unhandled_modal, false)
142
147
  browser.reset
148
+ base_url = @options[:base_url] || capybara_base_url
149
+ browser.base_url = base_url if base_url
143
150
  ensure
144
151
  @started = false
145
152
  end
@@ -147,7 +154,7 @@ module Capybara
147
154
  def save_screenshot(path, options = {})
148
155
  options[:scale] = @zoom_factor if @zoom_factor
149
156
 
150
- if pdf?(path, options)
157
+ if File.extname(path) == ".pdf"
151
158
  options[:paperWidth] = @paper_size[:width].to_f if @paper_size
152
159
  options[:paperHeight] = @paper_size[:height].to_f if @paper_size
153
160
  browser.pdf(path: path, **options)
@@ -155,32 +162,10 @@ module Capybara
155
162
  browser.screenshot(path: path, **options)
156
163
  end
157
164
  end
158
- alias render save_screenshot
159
-
160
- def render_base64(format = :png, options = {})
161
- if pdf?(nil, options)
162
- options[:paperWidth] = @paper_size[:width].to_f if @paper_size
163
- options[:paperHeight] = @paper_size[:height].to_f if @paper_size
164
- browser.pdf(encoding: :base64, **options)
165
- else
166
- browser.screenshot(format: format, encoding: :base64, **options)
167
- end
168
- end
169
-
170
- def zoom_factor=(value)
171
- @zoom_factor = value.to_f
172
- end
173
-
174
- attr_writer :paper_size
175
-
176
- def resize(width, height)
177
- browser.resize(width: width, height: height)
178
- end
179
- alias resize_window resize
180
165
 
181
166
  def resize_window_to(handle, width, height)
182
167
  within_window(handle) do
183
- resize(width, height)
168
+ browser.resize(width: width, height: height)
184
169
  end
185
170
  end
186
171
 
@@ -200,129 +185,10 @@ module Capybara
200
185
  end
201
186
  end
202
187
 
203
- def scroll_to(left, top)
204
- browser.mouse.scroll_to(left, top)
205
- end
206
-
207
- def network_traffic(type = nil)
208
- traffic = browser.network.traffic
209
-
210
- case type.to_s
211
- when "all"
212
- traffic
213
- when "blocked"
214
- traffic.select(&:blocked?)
215
- else
216
- # when request isn't blocked
217
- traffic.reject(&:blocked?)
218
- end
219
- end
220
-
221
- def clear_network_traffic
222
- browser.network.clear(:traffic)
223
- end
224
-
225
- def set_proxy(host, port, user = nil, password = nil, bypass = nil)
226
- @options.merge!(proxy: { host: host, port: port, user: user, password: password, bypass: bypass })
227
- end
228
-
229
- def headers
230
- browser.headers.get
231
- end
232
-
233
- def headers=(headers)
234
- browser.headers.set(headers)
235
- end
236
-
237
- def add_headers(headers)
238
- browser.headers.add(headers)
239
- end
240
-
241
- def add_header(name, value, permanent: true)
242
- browser.headers.add({ name => value }, permanent: permanent)
243
- end
244
-
245
188
  def response_headers
246
189
  browser.network.response&.headers
247
190
  end
248
191
 
249
- def cookies
250
- browser.cookies.all
251
- end
252
-
253
- def set_cookie(name, value, options = {})
254
- options = options.dup
255
- options[:name] ||= name
256
- options[:value] ||= value
257
- options[:domain] ||= default_domain
258
- browser.cookies.set(**options)
259
- end
260
-
261
- def remove_cookie(name, **options)
262
- options[:domain] = default_domain if options.empty?
263
- options = options.merge(name: name)
264
- browser.cookies.remove(**options)
265
- end
266
-
267
- def clear_cookies
268
- browser.cookies.clear
269
- end
270
-
271
- def wait_for_network_idle(**options)
272
- browser.network.wait_for_idle(**options)
273
- end
274
-
275
- def clear_memory_cache
276
- browser.network.clear(:cache)
277
- end
278
-
279
- def basic_authorize(user, password)
280
- browser.network.authorize(user: user, password: password) do |request, _index, _total|
281
- request.continue
282
- end
283
- end
284
- alias authorize basic_authorize
285
-
286
- def debug(...)
287
- browser.debug(...)
288
- end
289
-
290
- def pause
291
- # STDIN is not necessarily connected to a keyboard. It might even be closed.
292
- # So we need a method other than keypress to continue.
293
-
294
- # In jRuby - STDIN returns immediately from select
295
- # see https://github.com/jruby/jruby/issues/1783
296
- read, write = IO.pipe
297
- thread = Thread.new do
298
- IO.copy_stream($stdin, write)
299
- write.close
300
- end
301
-
302
- warn "Cuprite execution paused. Press enter (or run 'kill -CONT #{Process.pid}') to continue."
303
-
304
- signal = false
305
- old_trap = trap("SIGCONT") do
306
- signal = true
307
- warn "\nSignal SIGCONT received"
308
- end
309
- keyboard = read.wait_readable(1) until keyboard || signal # wait for data on STDIN or signal SIGCONT received
310
-
311
- unless signal
312
- begin
313
- input = read.read_nonblock(80) # clear out the read buffer
314
- puts unless input&.end_with?("\n")
315
- rescue EOFError, IO::WaitReadable
316
- # Ignore problems reading from STDIN.
317
- end
318
- end
319
- ensure
320
- thread.kill
321
- read.close
322
- trap("SIGCONT", old_trap) # Restore the previous signal handler, if there was one.
323
- warn "Continuing"
324
- end
325
-
326
192
  def wait?
327
193
  true
328
194
  end
@@ -338,37 +204,49 @@ module Capybara
338
204
  def accept_modal(type, options = {})
339
205
  case type
340
206
  when :alert, :confirm
341
- browser.accept_confirm
207
+ browser.page.accept_confirm
342
208
  when :prompt
343
- browser.accept_prompt(options[:with])
209
+ browser.page.accept_prompt(options[:with])
344
210
  end
345
211
 
346
212
  yield if block_given?
347
213
 
348
- browser.find_modal(options)
214
+ browser.page.find_modal(options)
349
215
  end
350
216
 
351
217
  def dismiss_modal(type, options = {})
352
218
  case type
353
219
  when :confirm
354
- browser.dismiss_confirm
220
+ browser.page.dismiss_confirm
355
221
  when :prompt
356
- browser.dismiss_prompt
222
+ browser.page.dismiss_prompt
357
223
  end
358
224
 
359
225
  yield if block_given?
360
226
 
361
- browser.find_modal(options)
227
+ browser.page.find_modal(options)
362
228
  end
363
229
 
364
230
  private
365
231
 
366
- def default_domain
367
- if @started
368
- URI.parse(browser.current_url).host
369
- else
370
- URI.parse(default_cookie_host).host || "127.0.0.1"
371
- end
232
+ attr_reader :screen_size
233
+
234
+ def find(method, selector)
235
+ Finder.find(browser, method, selector).map { |native| Node.new(self, native) }
236
+ end
237
+
238
+ def within_window(handle)
239
+ original = browser.current_window_handle
240
+ raise Ferrum::NoSuchPageError unless browser.window_handles.include?(handle)
241
+
242
+ browser.switch_to_window(handle)
243
+ yield
244
+ ensure
245
+ browser.switch_to_window(original)
246
+ end
247
+
248
+ def capybara_base_url
249
+ session_options.app_host || session&.server&.base_url
372
250
  end
373
251
 
374
252
  def native_args(args)
@@ -387,14 +265,6 @@ module Capybara
387
265
  end
388
266
  end
389
267
 
390
- def default_cookie_host
391
- if respond_to?(:session_options)
392
- session_options.app_host
393
- else
394
- Capybara.app_host
395
- end || ""
396
- end
397
-
398
268
  def unwrap_script_result(arg)
399
269
  case arg
400
270
  when Array
@@ -407,11 +277,6 @@ module Capybara
407
277
  arg
408
278
  end
409
279
  end
410
-
411
- def pdf?(path, options)
412
- (path && File.extname(path).delete(".") == "pdf") ||
413
- options[:format].to_s == "pdf"
414
- end
415
280
  end
416
281
  end
417
282
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Cuprite
5
+ module Finder
6
+ module_function
7
+
8
+ def find(browser, method, selector, within = nil)
9
+ # Capybara appends its text-match hint as `(expr)[...]`, but with `exact_text: true` the hint
10
+ # is empty and the result `(expr)[]` is invalid XPath. An empty predicate is never valid, so
11
+ # dropping it can't change a valid selector. Remove once teamcapybara/capybara#2860 is released.
12
+ selector = selector.delete_suffix("[]") if method == :xpath
13
+ nodes = browser.evaluate("_cuprite.find(...arguments)", *[method, selector, within].compact)
14
+ nodes.select(&:node?)
15
+ rescue Ferrum::JavaScriptError => e
16
+ raise InvalidSelector.new(e.response, method, selector) if e.class_name == "InvalidSelector"
17
+
18
+ raise
19
+ end
20
+ end
21
+ end
22
+ end