capybara 3.15.1 → 3.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 12db68d440542fd3dda74f6b7f2dfa36c2eb2b8e1fe973cabbbfe1ebed0fe199
4
- data.tar.gz: 9fbdd911c4193831996aa05a086fdfcc9012897e3640d4a4efe37c1df6679514
3
+ metadata.gz: e11c89bad9b8520ba44314b12052ee3b5845a3bd758a8b96bd29b2cda338a1e9
4
+ data.tar.gz: 8e52ab3d885733f71c1d0803d646890c788ec15cc8169c364c6497a21a299b54
5
5
  SHA512:
6
- metadata.gz: d93e604a136e740101b4672947033e4b628df4627337ed3aee11098f0fd1be7ee5f10a6401e6185ac9797a531db8dde6f7d51ca80a4d446468ecb77698140224
7
- data.tar.gz: ebdabe24db256bf3d083aafdb4dc16caf51211ae24cde2edca4cae755b97818ffc64f93705f45f98fc11f8e2e03184a2de09fc801dd85f500915cc91c5a0b52d
6
+ metadata.gz: 16bcc0e6278f54605652cfe31d98647c0426bba37a1d5695ea7970117de9b9410cfe5d3aa8c154d023979ff24250801d01a26c179aad7ae0acb2124e74e375de
7
+ data.tar.gz: e49ed2c6600f114a259025d5e05076a1e2b670b68611eed7d60f3304c15a0a9faf7e476b15dd63c46bc552bb145fcdeb45694a0aa1f71459346f851734c538c8
data/History.md CHANGED
@@ -1,11 +1,16 @@
1
- # Version 3.15.1
2
- Release date: 2019-04-20
1
+ # Version 3.16
2
+ Release date: 2019-03-28
3
+
4
+ ### Changed
5
+
6
+ * Ruby 2.4.0+ is now required
7
+ * Selenium driver now defaults to using a persistent http client connection
3
8
 
4
9
  ### Added
5
10
 
6
- * Suppress some deprecation notices from the latest selenium-webdriver
11
+ * :wait option in predicates now accepts `true` to selectively override when `Capybara.predicates_wait == false`
7
12
 
8
- # Version 3.15.0
13
+ # Version 3.15
9
14
  Release date: 2019-03-19
10
15
 
11
16
  ### Added
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jnicklas/capybara?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
7
  [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=capybara&package-manager=bundler&version-scheme=semver)](https://dependabot.com/compatibility-score.html?dependency-name=capybara&package-manager=bundler&version-scheme=semver)
8
8
 
9
- **Note** You are viewing the README for the 3.15.x version of Capybara.
9
+ **Note** You are viewing the README for the 3.16.x version of Capybara.
10
10
 
11
11
 
12
12
  Capybara helps you test web applications by simulating how a real user would
@@ -419,7 +419,7 @@ teamcapybara repo once completely stable.
419
419
 
420
420
  ### <a name="capybara-webkit"></a>Capybara-webkit
421
421
 
422
- Note: `capybara-webkit` depends on QtWebkit which went EOL quite some time ago. There has been an attempt to revive the project but `capybara-webkit` is not yet (AFAIK) compatible with the revived version of QtWebKit (could be a good OSS project for someone) and as such is still limited to an old version of QtWebKit. This means its support for modern JS and CSS is severely limited.
422
+ Note: `capybara-webkit` depends on QtWebkit which went end-of-life quite some time ago. There has been an attempt to revive the project but `capybara-webkit` is not yet (as far as I know) compatible with the revived version of QtWebKit (could be a good open source project for someone) and as such is still limited to an old version of QtWebKit. This means its support for modern JS and CSS is severely limited.
423
423
 
424
424
  The [capybara-webkit driver](https://github.com/thoughtbot/capybara-webkit) is for true headless
425
425
  testing. It uses QtWebKit to start a rendering engine process. It can execute JavaScript as well.
@@ -3,7 +3,7 @@
3
3
  module Capybara
4
4
  # @api private
5
5
  module Helpers
6
- module_function
6
+ module_function # rubocop:disable Layout/IndentationWidth
7
7
 
8
8
  ##
9
9
  # @deprecated
@@ -8,7 +8,7 @@ module Capybara
8
8
  # and continuously retry finding the element until either the element is found or the time
9
9
  # expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
10
10
  #
11
- # @option options [false, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
11
+ # @option options [false, true, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
12
12
 
13
13
  ##
14
14
  #
@@ -76,7 +76,7 @@ module Capybara
76
76
  def synchronize(seconds = nil, errors: nil)
77
77
  return yield if session.synchronized
78
78
 
79
- seconds = session_options.default_max_wait_time if seconds.nil?
79
+ seconds = session_options.default_max_wait_time if [nil, true].include? seconds
80
80
  session.synchronized = true
81
81
  timer = Capybara::Helpers.timer(expire_in: seconds)
82
82
  begin
@@ -13,7 +13,7 @@ module Capybara
13
13
  # and continuously retry finding the element until either the element is found or the time
14
14
  # expires. The length of time +find+ will wait is controlled through {Capybara.default_max_wait_time}
15
15
  # and defaults to 2 seconds.
16
- # @option options [false, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
16
+ # @option options [false, true, Numeric] wait (Capybara.default_max_wait_time) Maximum time to wait for matching element to appear.
17
17
  #
18
18
  # page.find('#foo').find('.bar')
19
19
  # page.find(:xpath, './/div[contains(., "bar")]')
@@ -98,7 +98,7 @@ module Capybara
98
98
 
99
99
  invalid_names = invalid_keys.map(&:inspect).join(', ')
100
100
  valid_names = valid_keys.map(&:inspect).join(', ')
101
- raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
101
+ raise ArgumentError, "Invalid option(s) #{invalid_names}, should be one of #{valid_names}"
102
102
  end
103
103
  end
104
104
  end
@@ -271,7 +271,7 @@ module Capybara
271
271
 
272
272
  def assert_valid_keys
273
273
  unless VALID_MATCH.include?(match)
274
- raise ArgumentError, "invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
274
+ raise ArgumentError, "Invalid option #{match.inspect} for :match, should be one of #{VALID_MATCH.map(&:inspect).join(', ')}"
275
275
  end
276
276
 
277
277
  unhandled_options = @options.keys.reject do |option_name|
@@ -284,7 +284,7 @@ module Capybara
284
284
 
285
285
  invalid_names = unhandled_options.map(&:inspect).join(', ')
286
286
  valid_names = (valid_keys - [:allow_self]).map(&:inspect).join(', ')
287
- raise ArgumentError, "invalid keys #{invalid_names}, should be one of #{valid_names}"
287
+ raise ArgumentError, "Invalid option(s) #{invalid_names}, should be one of #{valid_names}"
288
288
  end
289
289
 
290
290
  def filtered_expression(expr)
@@ -56,7 +56,7 @@ private
56
56
  end
57
57
 
58
58
  def request_method
59
- self[:method] =~ /post/i ? :post : :get
59
+ self[:method].to_s.match?(/post/i) ? :post : :get
60
60
  end
61
61
 
62
62
  def merge_param!(params, key, value)
@@ -107,7 +107,7 @@ module Capybara
107
107
  # See {Capybara::Node::Matchers#has_unchecked_field?}
108
108
 
109
109
  # RSpec matcher for text content
110
- # See {Capybara::SessionMatchers#assert_text}
110
+ # See {Capybara::Node::Matchers#assert_text}
111
111
  def have_text(*args)
112
112
  Matchers::HaveText.new(*args)
113
113
  end
@@ -459,100 +459,92 @@ Capybara.add_selector(:table, locator_type: [String, Symbol]) do
459
459
  expression_filter(:with_cols, valid_values: [Array]) do |xpath, cols|
460
460
  col_conditions = cols.map do |col|
461
461
  if col.is_a? Hash
462
- col.reduce(nil) do |xp, (header, cell)|
463
- header_xp = XPath.descendant(:th)[XPath.string.n.is(header)]
464
- cell_xp = XPath.descendant(:tr)[header_xp].descendant(:td)
465
- next cell_xp[XPath.string.n.is(cell)] unless xp
466
-
467
- table_ancestor = XPath.ancestor(:table)[1]
468
- xp = XPath::Expression.new(:join, table_ancestor, xp)
469
- cell_xp[XPath.string.n.is(cell) & XPath.position.equals(xp.preceding_sibling(:td).count.plus(1))]
462
+ col.reduce(nil) do |xp, (header, cell_str)|
463
+ header = XPath.descendant(:th)[XPath.string.n.is(header)]
464
+ td = XPath.descendant(:tr)[header].descendant(:td)
465
+ cell_condition = XPath.string.n.is(cell_str)
466
+ cell_condition &= prev_col_position?(XPath.ancestor(:table)[1].join(xp)) if xp
467
+ td[cell_condition]
470
468
  end
471
469
  else
472
- cells_xp = col.reduce(nil) do |xp, cell|
473
- cell_conditions = [XPath.string.n.is(cell)]
474
- if xp
475
- prev_row_xp = XPath::Expression.new(:join, XPath.ancestor(:tr)[1].preceding_sibling(:tr), xp)
476
- cell_conditions << XPath.position.equals(prev_row_xp.preceding_sibling(:td).count.plus(1))
470
+ cells_xp = col.reduce(nil) do |prev_cell, cell_str|
471
+ cell_condition = XPath.string.n.is(cell_str)
472
+
473
+ if prev_cell
474
+ prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell)
475
+ cell_condition &= prev_col_position?(prev_cell)
477
476
  end
478
- XPath.descendant(:td)[cell_conditions.reduce :&]
477
+
478
+ XPath.descendant(:td)[cell_condition]
479
479
  end
480
- XPath::Expression.new(:join, XPath.descendant(:tr), cells_xp)
480
+ XPath.descendant(:tr).join(cells_xp)
481
481
  end
482
482
  end.reduce(:&)
483
483
  xpath[col_conditions]
484
484
  end
485
485
 
486
486
  expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
487
- raise ArgumentError, ":cols must be an Array of Arrays" unless cols.all? { |col| col.is_a? Array }
487
+ raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all? { |col| col.is_a? Array }
488
488
 
489
489
  rows = cols.transpose
490
- xpath = xpath[XPath.descendant(:tbody).descendant(:tr).count.equals(rows.size) | (XPath.descendant(:tr).count.equals(rows.size) & ~XPath.descendant(:tbody))]
491
-
492
- col_conditions = rows.map do |row|
493
- row_conditions = row.map do |cell|
494
- XPath.self(:td)[XPath.string.n.is(cell)]
495
- end
496
- row_conditions = row_conditions.reverse.reduce do |cond, cell|
497
- cell[XPath.following_sibling[cond]]
498
- end
499
- row_xpath = XPath.descendant(:tr)[XPath.descendant(:td)[row_conditions]]
500
- row_xpath[XPath.descendant(:td).count.equals(row.size)]
501
- end.reduce(:&)
502
-
503
- xpath[col_conditions]
490
+ col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
491
+ xpath[match_row_count(rows.size)][col_conditions]
504
492
  end
505
493
 
506
494
  expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows|
507
- rows_conditions = rows.map do |row|
508
- if row.is_a? Hash
509
- row_conditions = row.map do |header, cell|
510
- header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
511
- XPath.descendant(:td)[
512
- XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
513
- ]
514
- end.reduce(:&)
515
- XPath.descendant(:tr)[row_conditions]
516
- else
517
- row_conditions = row.map do |cell|
518
- XPath.self(:td)[XPath.string.n.is(cell)]
519
- end
520
- row_conditions = row_conditions.reverse.reduce do |cond, cell|
521
- cell[XPath.following_sibling[cond]]
522
- end
523
- XPath.descendant(:tr)[XPath.descendant(:td)[row_conditions]]
524
- end
525
- end.reduce(:&)
495
+ rows_conditions = rows.map { |row| match_row(row) }.reduce(:&)
526
496
  xpath[rows_conditions]
527
497
  end
528
498
 
529
499
  expression_filter(:rows, valid_values: [Array]) do |xpath, rows|
530
- xpath = xpath[XPath.descendant(:tbody).descendant(:tr).count.equals(rows.size) | (XPath.descendant(:tr).count.equals(rows.size) & ~XPath.descendant(:tbody))]
531
- rows_conditions = rows.map do |row|
532
- row_xpath = if row.is_a? Hash
533
- row_conditions = row.map do |header, cell|
534
- header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
535
- XPath.descendant(:td)[
536
- XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
537
- ]
538
- end.reduce(:&)
539
- XPath.descendant(:tr)[row_conditions]
500
+ rows_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
501
+ xpath[match_row_count(rows.size)][rows_conditions]
502
+ end
503
+
504
+ describe_expression_filters do |caption: nil, **|
505
+ " with caption \"#{caption}\"" if caption
506
+ end
507
+
508
+ def prev_col_position?(cell)
509
+ XPath.position.equals(cell_position(cell))
510
+ end
511
+
512
+ def cell_position(cell)
513
+ cell.preceding_sibling(:td).count.plus(1)
514
+ end
515
+
516
+ def match_row(row, match_size: false)
517
+ xp = XPath.descendant(:tr)[
518
+ if row.is_a? Hash
519
+ row_match_cells_to_headers(row)
540
520
  else
541
- row_conditions = row.map do |cell|
542
- XPath.self(:td)[XPath.string.n.is(cell)]
543
- end
544
- row_conditions = row_conditions.reverse.reduce do |cond, cell|
545
- cell[XPath.following_sibling[cond]]
546
- end
547
- XPath.descendant(:tr)[XPath.descendant(:td)[row_conditions]]
521
+ XPath.descendant(:td)[row_match_ordered_cells(row)]
548
522
  end
549
- row_xpath[XPath.descendant(:td).count.equals(row.size)]
523
+ ]
524
+ xp = xp[XPath.descendant(:td).count.equals(row.size)] if match_size
525
+ xp
526
+ end
527
+
528
+ def match_row_count(size)
529
+ XPath.descendant(:tbody).descendant(:tr).count.equals(size) | (XPath.descendant(:tr).count.equals(size) & ~XPath.descendant(:tbody))
530
+ end
531
+
532
+ def row_match_cells_to_headers(row)
533
+ row.map do |header, cell|
534
+ header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[XPath.string.n.is(header)]
535
+ XPath.descendant(:td)[
536
+ XPath.string.n.is(cell) & XPath.position.equals(header_xp.preceding_sibling.count.plus(1))
537
+ ]
550
538
  end.reduce(:&)
551
- xpath[rows_conditions]
552
539
  end
553
540
 
554
- describe_expression_filters do |caption: nil, **|
555
- " with caption \"#{caption}\"" if caption
541
+ def row_match_ordered_cells(row)
542
+ row_conditions = row.map do |cell|
543
+ XPath.self(:td)[XPath.string.n.is(cell)]
544
+ end
545
+ row_conditions.reverse.reduce do |cond, cell|
546
+ cell[XPath.following_sibling[cond]]
547
+ end
556
548
  end
557
549
  end
558
550
 
@@ -7,13 +7,13 @@ module Capybara
7
7
  value = str.dup
8
8
  out = +''
9
9
  out << value.slice!(0...1) if value =~ /^[-_]/
10
- out << (value[0] =~ NMSTART ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
10
+ out << (value[0].match?(NMSTART) ? value.slice!(0...1) : escape_char(value.slice!(0...1)))
11
11
  out << value.gsub(/[^a-zA-Z0-9_-]/) { |char| escape_char char }
12
12
  out
13
13
  end
14
14
 
15
15
  def self.escape_char(char)
16
- char =~ %r{[ -/:-~]} ? "\\#{char}" : format('\\%06x', char.ord)
16
+ char.match?(%r{[ -/:-~]}) ? "\\#{char}" : format('\\%06x', char.ord)
17
17
  end
18
18
 
19
19
  def self.split(css)
@@ -7,3 +7,11 @@ module XPath
7
7
  end
8
8
  end
9
9
  end
10
+
11
+ module XPath
12
+ module DSL
13
+ def join(*expressions)
14
+ XPath::Expression.new(:join, *[self, expressions].flatten)
15
+ end
16
+ end
17
+ end
@@ -16,7 +16,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
16
16
 
17
17
  def self.load_selenium
18
18
  require 'selenium-webdriver'
19
- require 'capybara/selenium/logger_suppressor'
19
+ require 'capybara/selenium/patches/persistent_client'
20
20
  warn "Warning: You're using an unsupported version of selenium-webdriver, please upgrade." if Gem.loaded_specs['selenium-webdriver'].version < Gem::Version.new('3.5.0')
21
21
  rescue LoadError => err
22
22
  raise err if err.message !~ /selenium-webdriver/
@@ -26,8 +26,12 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
26
26
 
27
27
  def browser
28
28
  @browser ||= begin
29
- if options[:timeout]
30
- options[:http_client] ||= Selenium::WebDriver::Remote::Http::Default.new(read_timeout: options[:timeout])
29
+ options[:http_client] ||= begin
30
+ if options[:timeout]
31
+ ::Capybara::Selenium::PersistentClient.new(read_timeout: options[:timeout])
32
+ else
33
+ ::Capybara::Selenium::PersistentClient.new
34
+ end
31
35
  end
32
36
  processed_options = options.reject { |key, _val| SPECIAL_OPTIONS.include?(key) }
33
37
  Selenium::WebDriver.for(options[:browser], processed_options).tap do |driver|
@@ -111,7 +115,7 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
111
115
  navigated = true
112
116
  # Ensure the page is empty and trigger an UnhandledAlertError for any modals that appear during unload
113
117
  wait_for_empty_page(timer)
114
- rescue *unhandled_alert_errors
118
+ rescue Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError
115
119
  # This error is thrown if an unhandled alert is on the page
116
120
  # Firefox appears to automatically dismiss this alert, chrome does not
117
121
  # We'll try to accept it
@@ -178,7 +182,10 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
178
182
  browser.window_handles
179
183
  end
180
184
 
181
- def open_new_window
185
+ def open_new_window(kind = :tab)
186
+ browser.manage.new_window(kind)
187
+ rescue NoMethodError, Selenium::WebDriver::Error::WebDriverError
188
+ # If not supported by the driver or browser default to using JS
182
189
  browser.execute_script('window.open();')
183
190
  end
184
191
 
@@ -219,24 +226,19 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
219
226
  end
220
227
 
221
228
  def invalid_element_errors
222
- errors = [
229
+ [
223
230
  ::Selenium::WebDriver::Error::StaleElementReferenceError,
231
+ ::Selenium::WebDriver::Error::UnhandledError,
232
+ ::Selenium::WebDriver::Error::ElementNotVisibleError,
233
+ ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around a chromedriver go_back/go_forward race condition
224
234
  ::Selenium::WebDriver::Error::ElementNotInteractableError,
225
- ::Selenium::WebDriver::Error::InvalidSelectorError, # Work around chromedriver go_back/go_forward race condition
226
235
  ::Selenium::WebDriver::Error::ElementClickInterceptedError,
236
+ ::Selenium::WebDriver::Error::InvalidElementStateError,
237
+ ::Selenium::WebDriver::Error::ElementNotSelectableError,
238
+ ::Selenium::WebDriver::Error::ElementNotSelectableError,
227
239
  ::Selenium::WebDriver::Error::NoSuchElementError, # IE
228
240
  ::Selenium::WebDriver::Error::InvalidArgumentError # IE
229
241
  ]
230
-
231
- ::Selenium::WebDriver.logger.suppress_deprecations do
232
- errors.concat [
233
- ::Selenium::WebDriver::Error::UnhandledError,
234
- ::Selenium::WebDriver::Error::ElementNotVisibleError,
235
- ::Selenium::WebDriver::Error::InvalidElementStateError,
236
- ::Selenium::WebDriver::Error::ElementNotSelectableError
237
- ]
238
- end
239
- errors
240
242
  end
241
243
 
242
244
  def no_such_window_error
@@ -252,24 +254,12 @@ private
252
254
  def clear_browser_state
253
255
  delete_all_cookies
254
256
  clear_storage
255
- rescue *clear_browser_state_errors # rubocop:disable Lint/HandleExceptions
257
+ rescue Selenium::WebDriver::Error::UnhandledError # rubocop:disable Lint/HandleExceptions
256
258
  # delete_all_cookies fails when we've previously gone
257
259
  # to about:blank, so we rescue this error and do nothing
258
260
  # instead.
259
261
  end
260
262
 
261
- def clear_browser_state_errors
262
- ::Selenium::WebDriver.logger.suppress_deprecations do
263
- [Selenium::WebDriver::Error::UnhandledError, Selenium::WebDriver::Error::UnknownError]
264
- end
265
- end
266
-
267
- def unhandled_alert_errors
268
- ::Selenium::WebDriver.logger.suppress_deprecations do
269
- [Selenium::WebDriver::Error::UnhandledAlertError, Selenium::WebDriver::Error::UnexpectedAlertOpenError]
270
- end
271
- end
272
-
273
263
  def delete_all_cookies
274
264
  @browser.manage.delete_all_cookies
275
265
  end
@@ -340,19 +330,13 @@ private
340
330
  wait.until do
341
331
  alert = @browser.switch_to.alert
342
332
  regexp = text.is_a?(Regexp) ? text : Regexp.escape(text.to_s)
343
- alert.text.match(regexp) ? alert : nil
333
+ alert.text.match?(regexp) ? alert : nil
344
334
  end
345
- rescue *find_modal_errors
335
+ rescue Selenium::WebDriver::Error::TimeOutError
346
336
  raise Capybara::ModalNotFound, "Unable to find modal dialog#{" with #{text}" if text}"
347
337
  end
348
338
  end
349
339
 
350
- def find_modal_errors
351
- ::Selenium::WebDriver.logger.suppress_deprecations do
352
- [Selenium::WebDriver::Error::TimeoutError, Selenium::WebDriver::Error::TimeOutError]
353
- end
354
- end
355
-
356
340
  def silenced_unknown_error_message?(msg)
357
341
  silenced_unknown_error_messages.any? { |regex| msg =~ regex }
358
342
  end
@@ -366,7 +350,7 @@ private
366
350
  when Array
367
351
  arg.map { |arr| unwrap_script_result(arr) }
368
352
  when Hash
369
- arg.each { |key, value| arg[key] = unwrap_script_result(value) }
353
+ arg.transform_values! { |value| unwrap_script_result(value) }
370
354
  when Selenium::WebDriver::Element
371
355
  build_node(arg)
372
356
  else
@@ -40,17 +40,11 @@ private
40
40
 
41
41
  def delete_all_cookies
42
42
  execute_cdp('Network.clearBrowserCookies')
43
- rescue *cdp_unsupported_errors
43
+ rescue Selenium::WebDriver::Error::UnhandledError, Selenium::WebDriver::Error::WebDriverError
44
44
  # If the CDP clear isn't supported do original limited clear
45
45
  super
46
46
  end
47
47
 
48
- def cdp_unsupported_errors
49
- ::Selenium::WebDriver.logger.suppress_deprecations do
50
- [Selenium::WebDriver::Error::UnhandledError, Selenium::WebDriver::Error::WebDriverError]
51
- end
52
- end
53
-
54
48
  def execute_cdp(cmd, params = {})
55
49
  args = { cmd: cmd, params: params }
56
50
  result = bridge.http.call(:post, "session/#{bridge.session_id}/goog/cdp/execute", args)
@@ -3,7 +3,7 @@
3
3
  require 'capybara/selenium/nodes/safari_node'
4
4
 
5
5
  module Capybara::Selenium::Driver::SafariDriver
6
- private
6
+ private # rubocop:disable Layout/IndentationWidth
7
7
 
8
8
  def build_node(native_node, initial_cache = {})
9
9
  ::Capybara::Selenium::SafariNode.new(self, native_node, initial_cache)
@@ -2,7 +2,9 @@
2
2
 
3
3
  class Capybara::Selenium::Node
4
4
  module Html5Drag
5
- private
5
+ # Implement methods to emulate HTML5 drag and drop
6
+
7
+ private # rubocop:disable Layout/IndentationWidth
6
8
 
7
9
  def html5_drag_to(element)
8
10
  driver.execute_script MOUSEDOWN_TRACKER
@@ -14,10 +14,8 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
14
14
 
15
15
  def set_file(value) # rubocop:disable Naming/AccessorMethodName
16
16
  super(value)
17
- rescue *file_errors => err
18
- if err.message =~ /File not found : .+\n.+/m
19
- raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload" if e.message.match?(/File not found : .+\n.+/m)
20
- end
17
+ rescue ::Selenium::WebDriver::Error::ExpectedError => err
18
+ raise ArgumentError, "Selenium < 3.14 with remote Chrome doesn't support multiple file upload" if err.message.match?(/File not found : .+\n.+/m)
21
19
 
22
20
  raise
23
21
  end
@@ -30,12 +28,6 @@ class Capybara::Selenium::ChromeNode < Capybara::Selenium::Node
30
28
 
31
29
  private
32
30
 
33
- def file_errors
34
- ::Selenium::WebDriver.logger.suppress_deprecations do
35
- [::Selenium::WebDriver::Error::ExpectedError]
36
- end
37
- end
38
-
39
31
  def bridge
40
32
  driver.browser.send(:bridge)
41
33
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Selenium
5
+ class PersistentClient < ::Selenium::WebDriver::Remote::Http::Default
6
+ def close
7
+ super
8
+ @http.finish if @http&.started?
9
+ end
10
+
11
+ private
12
+
13
+ def http
14
+ super.tap do |http|
15
+ http.start unless http.started?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -263,7 +263,7 @@ module Capybara
263
263
 
264
264
  if base_uri && [nil, 'http', 'https'].include?(visit_uri.scheme)
265
265
  if visit_uri.relative?
266
- visit_uri_parts = visit_uri.to_hash.delete_if { |_k, value| value.nil? }
266
+ visit_uri_parts = visit_uri.to_hash.compact
267
267
 
268
268
  # Useful to people deploying to a subdirectory
269
269
  # and/or single page apps where only the url fragment changes
@@ -458,9 +458,13 @@ module Capybara
458
458
  #
459
459
  # @return [Capybara::Window] window that has been opened
460
460
  #
461
- def open_new_window
461
+ def open_new_window(kind = :tab)
462
462
  window_opened_by do
463
- driver.open_new_window
463
+ if driver.method(:open_new_window).arity.zero?
464
+ driver.open_new_window
465
+ else
466
+ driver.open_new_window(kind)
467
+ end
464
468
  end
465
469
  end
466
470
 
@@ -850,7 +854,7 @@ module Capybara
850
854
  when Array
851
855
  arg.map { |subarg| element_script_result(subarg) }
852
856
  when Hash
853
- arg.each { |key, value| arg[key] = element_script_result(value) }
857
+ arg.transform_values! { |value| element_script_result(value) }
854
858
  when Capybara::Driver::Node
855
859
  Capybara::Node::Element.new(self, arg, nil, nil)
856
860
  else
@@ -108,7 +108,7 @@ Capybara::SpecHelper.spec '#find' do
108
108
 
109
109
  it 'should warn if passed a non-valid locator type' do
110
110
  expect_any_instance_of(Kernel).to receive(:warn).with(/must respond to to_xpath or be an instance of String/)
111
- expect { @session.find(:xpath, 123) }.to raise_error # rubocop:disable RSpec/UnspecifiedException
111
+ expect { @session.find(:xpath, 123) }.to raise_error Exception # The exact error is not yet well defined
112
112
  end
113
113
  end
114
114
 
@@ -113,13 +113,20 @@ Capybara::SpecHelper.spec '#has_css?' do
113
113
  end
114
114
 
115
115
  context 'with predicates_wait == false' do
116
- it 'should not wait for content to appear', requires: [:js] do
116
+ before do
117
117
  Capybara.predicates_wait = false
118
- Capybara.default_max_wait_time = 2
118
+ Capybara.default_max_wait_time = 5
119
119
  @session.visit('/with_js')
120
120
  @session.click_link('Click me')
121
+ end
122
+
123
+ it 'should not wait for content to appear', requires: [:js] do
121
124
  expect(@session.has_css?("input[type='submit'][value='New Here']")).to be false
122
125
  end
126
+
127
+ it 'should should the default wait time if true is passed for :wait', requires: [:js] do
128
+ expect(@session.has_css?("input[type='submit'][value='New Here']", wait: true)).to be true
129
+ end
123
130
  end
124
131
 
125
132
  context 'with between' do
@@ -49,8 +49,8 @@ Capybara::SpecHelper.spec '#has_table?' do
49
49
  %w[Thomas Walpole Oceanside],
50
50
  %w[Danilo Wilkinson Johnsonville],
51
51
  %w[Vern Konopelski Everette],
52
- ["Ratke", "Lawrence", "East Sorayashire"],
53
- ["Palmer", "Sawayn", "West Trinidad"]
52
+ ['Ratke', 'Lawrence', 'East Sorayashire'],
53
+ ['Palmer', 'Sawayn', 'West Trinidad']
54
54
  ])
55
55
  end
56
56
 
@@ -84,8 +84,8 @@ Capybara::SpecHelper.spec '#has_table?' do
84
84
  %w[Thomas Walpole Oceanside],
85
85
  %w[Danilo Wilkinson Johnsonville],
86
86
  %w[Vern Konopelski Everette],
87
- ["Ratke", "Lawrence", "East Sorayashire"],
88
- ["Palmer", "Sawayn", "West Trinidad"]
87
+ ['Ratke', 'Lawrence', 'East Sorayashire'],
88
+ ['Palmer', 'Sawayn', 'West Trinidad']
89
89
  ])
90
90
  end
91
91
 
@@ -100,7 +100,7 @@ module Capybara
100
100
 
101
101
  def silence_stream(stream)
102
102
  old_stream = stream.dup
103
- stream.reopen(RbConfig::CONFIG['host_os'] =~ /rmswin|mingw/ ? 'NUL:' : '/dev/null')
103
+ stream.reopen(RbConfig::CONFIG['host_os'].match?(/rmswin|mingw/) ? 'NUL:' : '/dev/null')
104
104
  stream.sync = true
105
105
  yield
106
106
  ensure
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Capybara
4
- VERSION = '3.15.1'
4
+ VERSION = '3.16.0'
5
5
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'selenium-webdriver'
5
+
6
+ require 'sauce_whisk'
7
+ # require 'shared_selenium_session'
8
+ # require 'rspec/shared_spec_matchers'
9
+
10
+ Capybara.register_driver :sauce_chrome do |app|
11
+ options = {
12
+ selenium_version: '3.141.59',
13
+ platform: 'macOS 10.12',
14
+ browser_name: 'chrome',
15
+ version: '65.0',
16
+ name: 'Capybara test',
17
+ build: ENV['TRAVIS_REPO_SLUG'] || "Ruby-RSpec-Selenium: Local-#{Time.now.to_i}",
18
+ username: ENV['SAUCE_USERNAME'],
19
+ access_key: ENV['SAUCE_ACCESS_KEY']
20
+ }
21
+
22
+ options.delete(:browser_name)
23
+
24
+ capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(options)
25
+ url = 'https://ondemand.saucelabs.com:443/wd/hub'
26
+
27
+ Capybara::Selenium::Driver.new(app,
28
+ browser: :remote, url: url,
29
+ desired_capabilities: capabilities,
30
+ options: Selenium::WebDriver::Chrome::Options.new(args: ['']))
31
+ end
32
+
33
+ CHROME_REMOTE_DRIVER = :sauce_chrome
34
+
35
+ module TestSessions
36
+ Chrome = Capybara::Session.new(CHROME_REMOTE_DRIVER, TestApp)
37
+ end
38
+
39
+ skipped_tests = %i[response_headers status_code trigger download]
40
+
41
+ Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example|
42
+ end
@@ -474,8 +474,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
474
474
  it 'can set and clear a text field' do
475
475
  session.visit 'https://reactjs.org/docs/forms.html'
476
476
  session.all(:css, 'h2#controlled-components ~ p a', text: 'Try it on CodePen')[0].click
477
- sleep 2 # give codepen a chance to stabilize result frame
478
- session.within_frame(:css, 'iframe.result-iframe') do
477
+ session.within_frame(:css, 'iframe.result-iframe[src^="https://s.codepen.io"]', wait: 10) do
479
478
  session.fill_in('Name:', with: 'abc')
480
479
  session.accept_prompt 'A name was submitted: abc' do
481
480
  session.click_button('Submit')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.15.1
4
+ version: 3.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thomas Walpole
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain:
12
12
  - gem-public_cert.pem
13
- date: 2019-04-20 00:00:00.000000000 Z
13
+ date: 2019-03-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: addressable
@@ -264,6 +264,20 @@ dependencies:
264
264
  - - ">="
265
265
  - !ruby/object:Gem::Version
266
266
  version: '0'
267
+ - !ruby/object:Gem::Dependency
268
+ name: rubocop-performance
269
+ requirement: !ruby/object:Gem::Requirement
270
+ requirements:
271
+ - - ">="
272
+ - !ruby/object:Gem::Version
273
+ version: '0'
274
+ type: :development
275
+ prerelease: false
276
+ version_requirements: !ruby/object:Gem::Requirement
277
+ requirements:
278
+ - - ">="
279
+ - !ruby/object:Gem::Version
280
+ version: '0'
267
281
  - !ruby/object:Gem::Dependency
268
282
  name: rubocop-rspec
269
283
  requirement: !ruby/object:Gem::Requirement
@@ -278,6 +292,20 @@ dependencies:
278
292
  - - ">="
279
293
  - !ruby/object:Gem::Version
280
294
  version: '0'
295
+ - !ruby/object:Gem::Dependency
296
+ name: sauce_whisk
297
+ requirement: !ruby/object:Gem::Requirement
298
+ requirements:
299
+ - - ">="
300
+ - !ruby/object:Gem::Version
301
+ version: '0'
302
+ type: :development
303
+ prerelease: false
304
+ version_requirements: !ruby/object:Gem::Requirement
305
+ requirements:
306
+ - - ">="
307
+ - !ruby/object:Gem::Version
308
+ version: '0'
281
309
  - !ruby/object:Gem::Dependency
282
310
  name: selenium-webdriver
283
311
  requirement: !ruby/object:Gem::Requirement
@@ -414,12 +442,12 @@ files:
414
442
  - lib/capybara/selenium/extensions/find.rb
415
443
  - lib/capybara/selenium/extensions/html5_drag.rb
416
444
  - lib/capybara/selenium/extensions/scroll.rb
417
- - lib/capybara/selenium/logger_suppressor.rb
418
445
  - lib/capybara/selenium/node.rb
419
446
  - lib/capybara/selenium/nodes/chrome_node.rb
420
447
  - lib/capybara/selenium/nodes/firefox_node.rb
421
448
  - lib/capybara/selenium/nodes/safari_node.rb
422
449
  - lib/capybara/selenium/patches/pause_duration_fix.rb
450
+ - lib/capybara/selenium/patches/persistent_client.rb
423
451
  - lib/capybara/server.rb
424
452
  - lib/capybara/server/animation_disabler.rb
425
453
  - lib/capybara/server/checker.rb
@@ -585,6 +613,7 @@ files:
585
613
  - spec/rspec/views_spec.rb
586
614
  - spec/rspec_matchers_spec.rb
587
615
  - spec/rspec_spec.rb
616
+ - spec/sauce_spec_chrome.rb
588
617
  - spec/selector_spec.rb
589
618
  - spec/selenium_spec_chrome.rb
590
619
  - spec/selenium_spec_chrome_remote.rb
@@ -612,7 +641,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
612
641
  requirements:
613
642
  - - ">="
614
643
  - !ruby/object:Gem::Version
615
- version: 2.3.0
644
+ version: 2.4.0
616
645
  required_rubygems_version: !ruby/object:Gem::Requirement
617
646
  requirements:
618
647
  - - ">="
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Capybara
4
- module Selenium
5
- module DeprecationSuppressor
6
- def deprecate(*)
7
- super unless @suppress_for_capybara
8
- end
9
-
10
- def suppress_deprecations
11
- prev_suppress_for_capybara, @suppress_for_capybara = @suppress_for_capybara, true
12
- yield
13
- ensure
14
- @suppress_for_capybara = prev_suppress_for_capybara
15
- end
16
- end
17
-
18
- module ErrorSuppressor
19
- def for_code(*)
20
- ::Selenium::WebDriver.logger.suppress_deprecations do
21
- super
22
- end
23
- end
24
- end
25
- end
26
- end
27
-
28
- Selenium::WebDriver::Logger.prepend Capybara::Selenium::DeprecationSuppressor
29
- Selenium::WebDriver::Error.singleton_class.prepend Capybara::Selenium::ErrorSuppressor