capybara 3.11.1 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +18 -1
  3. data/README.md +4 -4
  4. data/lib/capybara.rb +7 -0
  5. data/lib/capybara/node/element.rb +7 -1
  6. data/lib/capybara/node/matchers.rb +0 -18
  7. data/lib/capybara/queries/base_query.rb +1 -1
  8. data/lib/capybara/queries/selector_query.rb +24 -39
  9. data/lib/capybara/result.rb +4 -4
  10. data/lib/capybara/selector.rb +7 -15
  11. data/lib/capybara/selector/builders/css_builder.rb +59 -27
  12. data/lib/capybara/selector/builders/xpath_builder.rb +50 -35
  13. data/lib/capybara/selector/css.rb +7 -7
  14. data/lib/capybara/selector/filter.rb +1 -0
  15. data/lib/capybara/selector/filter_set.rb +17 -15
  16. data/lib/capybara/selector/filters/locator_filter.rb +19 -0
  17. data/lib/capybara/selector/regexp_disassembler.rb +104 -61
  18. data/lib/capybara/selector/selector.rb +14 -5
  19. data/lib/capybara/selenium/driver.rb +14 -9
  20. data/lib/capybara/selenium/driver_specializations/{marionette_driver.rb → firefox_driver.rb} +3 -3
  21. data/lib/capybara/selenium/nodes/{marionette_node.rb → firefox_node.rb} +1 -1
  22. data/lib/capybara/spec/session/all_spec.rb +8 -1
  23. data/lib/capybara/spec/session/assert_selector_spec.rb +0 -10
  24. data/lib/capybara/spec/session/click_button_spec.rb +5 -3
  25. data/lib/capybara/spec/session/click_link_spec.rb +5 -5
  26. data/lib/capybara/spec/session/find_spec.rb +1 -1
  27. data/lib/capybara/spec/session/first_spec.rb +1 -1
  28. data/lib/capybara/spec/session/has_css_spec.rb +7 -0
  29. data/lib/capybara/spec/session/has_xpath_spec.rb +17 -0
  30. data/lib/capybara/spec/session/node_spec.rb +10 -3
  31. data/lib/capybara/spec/session/window/window_spec.rb +2 -2
  32. data/lib/capybara/spec/spec_helper.rb +1 -2
  33. data/lib/capybara/spec/views/obscured.erb +3 -0
  34. data/lib/capybara/version.rb +1 -1
  35. data/spec/css_builder_spec.rb +99 -0
  36. data/spec/result_spec.rb +6 -0
  37. data/spec/selector_spec.rb +26 -1
  38. data/spec/selenium_spec_chrome.rb +18 -16
  39. data/spec/selenium_spec_chrome_remote.rb +0 -2
  40. data/spec/{selenium_spec_marionette.rb → selenium_spec_firefox.rb} +31 -25
  41. data/spec/selenium_spec_firefox_remote.rb +4 -6
  42. data/spec/shared_selenium_session.rb +2 -2
  43. data/spec/spec_helper.rb +5 -5
  44. data/spec/xpath_builder_spec.rb +91 -0
  45. metadata +10 -7
@@ -72,6 +72,12 @@ RSpec.describe Capybara::Result do
72
72
  expect(result[-1].text).to eq 'Delta'
73
73
  expect(result[-2, 3].map(&:text)).to eq %w[Gamma Delta]
74
74
  expect(result[1..7].map(&:text)).to eq %w[Beta Gamma Delta]
75
+ expect(result[1...3].map(&:text)).to eq %w[Beta Gamma]
76
+ expect(result[2..-1].map(&:text)).to eq %w[Gamma Delta]
77
+ expect(result[2...-1].map(&:text)).to eq %w[Gamma]
78
+ eval <<~TEST, binding, __FILE__, __LINE__ + 1 if RUBY_VERSION.to_f > 2.5
79
+ expect(result[2..].map(&:text)).to eq %w[Gamma Delta]
80
+ TEST
75
81
  end
76
82
 
77
83
  it 'works with filter blocks' do
@@ -75,6 +75,7 @@ RSpec.describe Capybara do
75
75
 
76
76
  Capybara.add_selector :custom_xpath_selector do
77
77
  xpath(:valid1, :valid2) { |selector| selector }
78
+ match { |value| value == 'match_me' }
78
79
  end
79
80
  end
80
81
 
@@ -120,6 +121,26 @@ RSpec.describe Capybara do
120
121
  end
121
122
  end
122
123
 
124
+ describe '::[]' do
125
+ it 'can find a selector' do
126
+ expect(Capybara::Selector[:field]).not_to be_nil
127
+ end
128
+
129
+ it 'raises if no selector found' do
130
+ expect { Capybara::Selector[:no_exist] }.to raise_error(ArgumentError, /Unknown selector type/)
131
+ end
132
+ end
133
+
134
+ describe '::for' do
135
+ it 'finds selector that matches the locator' do
136
+ expect(Capybara::Selector.for('match_me').name).to eq :custom_xpath_selector
137
+ end
138
+
139
+ it 'returns nil if no match' do
140
+ expect(Capybara::Selector.for('nothing')).to be nil
141
+ end
142
+ end
143
+
123
144
  describe 'xpath' do
124
145
  it 'uses filter names passed in' do
125
146
  selector = Capybara::Selector.new :test do
@@ -423,12 +444,16 @@ RSpec.describe Capybara do
423
444
  expression_filter(:random) { |xpath, _| xpath } # do nothing filter
424
445
  end
425
446
  example.run
426
- Capybara::Selector.all[:link_or_button].expression_filters.delete(:random)
447
+ Capybara::Selector[:link_or_button].expression_filters.delete(:random)
427
448
  end
428
449
 
429
450
  context 'when modified' do
430
451
  it 'should still work' do
452
+ filter = Capybara::Selector[:link_or_button].expression_filters[:random]
453
+ allow(filter).to receive(:apply_filter).and_call_original
454
+
431
455
  expect(string.find(:link_or_button, 'click me', random: 'blah').value).to eq 'click me'
456
+ expect(filter).to have_received(:apply_filter).with(anything, :random, 'blah', anything)
432
457
  end
433
458
  end
434
459
  end
@@ -12,17 +12,17 @@ browser_options.headless! if ENV['HEADLESS']
12
12
  browser_options.add_option(:w3c, !!ENV['W3C'])
13
13
 
14
14
  Capybara.register_driver :selenium_chrome do |app|
15
- Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options).tap do |driver|
15
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: browser_options, timeout: 30).tap do |driver|
16
16
  driver.browser.download_path = Capybara.save_path
17
17
  end
18
18
  end
19
19
 
20
- Capybara.register_driver :selenium_chrome_clear_storage do |app|
20
+ Capybara.register_driver :selenium_chrome_not_clear_storage do |app|
21
21
  chrome_options = {
22
22
  browser: :chrome,
23
23
  options: browser_options
24
24
  }
25
- Capybara::Selenium::Driver.new(app, chrome_options.merge(clear_local_storage: true, clear_session_storage: true))
25
+ Capybara::Selenium::Driver.new(app, chrome_options.merge(clear_local_storage: false, clear_session_storage: false))
26
26
  end
27
27
 
28
28
  module TestSessions
@@ -30,8 +30,6 @@ module TestSessions
30
30
  end
31
31
 
32
32
  skipped_tests = %i[response_headers status_code trigger]
33
- # skip window tests when headless for now - closing a window not supported by chromedriver/chrome
34
- skipped_tests << :windows if ENV['TRAVIS'] && (ENV['SKIP_WINDOW'] || ENV['HEADLESS'])
35
33
 
36
34
  $stdout.puts `#{Selenium::WebDriver::Chrome.driver_path} --version` if ENV['CI']
37
35
 
@@ -39,6 +37,8 @@ Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_DRIVER.to_s, capybar
39
37
  case example.metadata[:full_description]
40
38
  when /#click_link can download a file$/
41
39
  skip 'Need to figure out testing of file downloading on windows platform' if Gem.win_platform?
40
+ when /Capybara::Session selenium_chrome Capybara::Window#maximize/
41
+ pending "Chrome headless doesn't support maximize" if ENV['HEADLESS']
42
42
  end
43
43
  end
44
44
 
@@ -49,32 +49,34 @@ RSpec.describe 'Capybara::Session with chrome' do
49
49
 
50
50
  context 'storage' do
51
51
  describe '#reset!' do
52
- it 'does not clear either storage by default' do
52
+ it 'clears storage by default' do
53
53
  @session = TestSessions::Chrome
54
54
  @session.visit('/with_js')
55
55
  @session.find(:css, '#set-storage').click
56
56
  @session.reset!
57
57
  @session.visit('/with_js')
58
- # expect(@session.driver.browser.local_storage.keys).not_to be_empty
59
- # expect(@session.driver.browser.session_storage.keys).not_to be_empty
60
- expect(@session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
61
- expect(@session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
58
+ expect(@session.evaluate_script('Object.keys(localStorage)')).to be_empty
59
+ expect(@session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
62
60
  end
63
61
 
64
- it 'clears storage when set' do
65
- @session = Capybara::Session.new(:selenium_chrome_clear_storage, TestApp)
62
+ it 'does not clear storage when false' do
63
+ @session = Capybara::Session.new(:selenium_chrome_not_clear_storage, TestApp)
66
64
  @session.visit('/with_js')
67
65
  @session.find(:css, '#set-storage').click
68
66
  @session.reset!
69
67
  @session.visit('/with_js')
70
- # expect(@session.driver.browser.local_storage.keys).to be_empty
71
- # expect(@session.driver.browser.session_storage.keys).to be_empty
72
- expect(@session.evaluate_script('Object.keys(localStorage)')).to be_empty
73
- expect(@session.evaluate_script('Object.keys(sessionStorage)')).to be_empty
68
+ expect(@session.evaluate_script('Object.keys(localStorage)')).not_to be_empty
69
+ expect(@session.evaluate_script('Object.keys(sessionStorage)')).not_to be_empty
74
70
  end
75
71
  end
76
72
  end
77
73
 
74
+ context 'timeout' do
75
+ it 'sets the http client read timeout' do
76
+ expect(TestSessions::Chrome.driver.browser.send(:bridge).http.read_timeout).to eq 30
77
+ end
78
+ end
79
+
78
80
  describe 'filling in Chrome-specific date and time fields with keystrokes' do
79
81
  let(:datetime) { Time.new(1983, 6, 19, 6, 30) }
80
82
 
@@ -60,8 +60,6 @@ TestSessions::Chrome.driver.browser.file_detector = lambda do |args|
60
60
  end
61
61
 
62
62
  skipped_tests = %i[response_headers status_code trigger download]
63
- # skip window tests when headless for now - closing a window not supported by chromedriver/chrome
64
- skipped_tests << :windows if ENV['TRAVIS'] && (ENV['SKIP_WINDOW'] || ENV['HEADLESS'])
65
63
 
66
64
  Capybara::SpecHelper.run_specs TestSessions::Chrome, CHROME_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example|
67
65
  case example.metadata[:full_description]
@@ -15,51 +15,51 @@ browser_options.profile = Selenium::WebDriver::Firefox::Profile.new.tap do |prof
15
15
  profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/csv'
16
16
  end
17
17
 
18
- Capybara.register_driver :selenium_marionette do |app|
18
+ Capybara.register_driver :selenium_firefox do |app|
19
19
  # ::Selenium::WebDriver.logger.level = "debug"
20
20
  Capybara::Selenium::Driver.new(
21
21
  app,
22
22
  browser: :firefox,
23
23
  options: browser_options,
24
+ timeout: 31,
24
25
  # Get a trace level log from geckodriver
25
26
  # :driver_opts => { args: ['-vv'] }
26
27
  )
27
28
  end
28
29
 
29
- Capybara.register_driver :selenium_marionette_clear_storage do |app|
30
+ Capybara.register_driver :selenium_firefox_not_clear_storage do |app|
30
31
  Capybara::Selenium::Driver.new(
31
32
  app,
32
33
  browser: :firefox,
33
- clear_local_storage: true,
34
- clear_session_storage: true,
34
+ clear_local_storage: false,
35
+ clear_session_storage: false,
35
36
  options: browser_options
36
37
  )
37
38
  end
38
39
 
39
40
  module TestSessions
40
- SeleniumMarionette = Capybara::Session.new(:selenium_marionette, TestApp)
41
+ SeleniumFirefox = Capybara::Session.new(:selenium_firefox, TestApp)
41
42
  end
42
43
 
43
44
  skipped_tests = %i[response_headers status_code trigger]
44
- skipped_tests << :windows if ENV['TRAVIS'] && ENV['SKIP_WINDOW']
45
45
 
46
46
  $stdout.puts `#{Selenium::WebDriver::Firefox.driver_path} --version` if ENV['CI']
47
47
 
48
- Capybara::SpecHelper.run_specs TestSessions::SeleniumMarionette, 'selenium', capybara_skip: skipped_tests do |example|
48
+ Capybara::SpecHelper.run_specs TestSessions::SeleniumFirefox, 'selenium', capybara_skip: skipped_tests do |example|
49
49
  case example.metadata[:full_description]
50
50
  when 'Capybara::Session selenium node #click should allow multiple modifiers'
51
- pending "Firefox doesn't generate an event for shift+control+click" if marionette_gte?(62, @session) && !Gem.win_platform?
51
+ pending "Firefox doesn't generate an event for shift+control+click" if firefox_gte?(62, @session) && !Gem.win_platform?
52
52
  when /^Capybara::Session selenium node #double_click/
53
- pending "selenium-webdriver/geckodriver doesn't generate double click event" if marionette_lt?(59, @session)
53
+ pending "selenium-webdriver/geckodriver doesn't generate double click event" if firefox_lt?(59, @session)
54
54
  when 'Capybara::Session selenium #accept_prompt should accept the prompt with a blank response when there is a default'
55
- pending "Geckodriver doesn't set a blank response in FF < 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1486485" if marionette_lt?(63, @session)
55
+ pending "Geckodriver doesn't set a blank response in FF < 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1486485" if firefox_lt?(63, @session)
56
56
  when 'Capybara::Session selenium #attach_file with multipart form should fire change once for each set of files uploaded'
57
57
  pending 'Gekcodriver appends files so we have to first call clear for multiple files which creates an extra change ' \
58
58
  'if files are already set'
59
59
  when 'Capybara::Session selenium #attach_file with multipart form should fire change once when uploading multiple files from empty'
60
- pending "FF < 62 doesn't support setting all files at once" if marionette_lt?(62, @session)
60
+ pending "FF < 62 doesn't support setting all files at once" if firefox_lt?(62, @session)
61
61
  when 'Capybara::Session selenium #accept_confirm should work with nested modals'
62
- skip 'Broken in FF 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if marionette_gte?(63, @session)
62
+ skip 'Broken in FF 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if firefox_gte?(63, @session)
63
63
  when 'Capybara::Session selenium #click_link can download a file'
64
64
  skip 'Need to figure out testing of file downloading on windows platform' if Gem.win_platform?
65
65
  when 'Capybara::Session selenium #reset_session! removes ALL cookies'
@@ -69,8 +69,8 @@ end
69
69
 
70
70
  RSpec.describe 'Capybara::Session with firefox' do # rubocop:disable RSpec/MultipleDescribes
71
71
  include Capybara::SpecHelper
72
- include_examples 'Capybara::Session', TestSessions::SeleniumMarionette, :selenium_marionette
73
- include_examples Capybara::RSpecMatchers, TestSessions::SeleniumMarionette, :selenium_marionette
72
+ include_examples 'Capybara::Session', TestSessions::SeleniumFirefox, :selenium_firefox
73
+ include_examples Capybara::RSpecMatchers, TestSessions::SeleniumFirefox, :selenium_firefox
74
74
  end
75
75
 
76
76
  RSpec.describe Capybara::Selenium::Driver do
@@ -125,33 +125,39 @@ RSpec.describe Capybara::Selenium::Driver do
125
125
 
126
126
  context 'storage' do
127
127
  describe '#reset!' do
128
- it 'does not clear either storage by default' do
129
- @session = TestSessions::SeleniumMarionette
128
+ it 'clears storage by default' do
129
+ @session = TestSessions::SeleniumFirefox
130
130
  @session.visit('/with_js')
131
131
  @session.find(:css, '#set-storage').click
132
132
  @session.reset!
133
133
  @session.visit('/with_js')
134
- expect(@session.driver.browser.local_storage.keys).not_to be_empty
135
- expect(@session.driver.browser.session_storage.keys).not_to be_empty
134
+ expect(@session.driver.browser.local_storage.keys).to be_empty
135
+ expect(@session.driver.browser.session_storage.keys).to be_empty
136
136
  end
137
137
 
138
- it 'clears storage when set' do
139
- @session = Capybara::Session.new(:selenium_marionette_clear_storage, TestApp)
138
+ it 'does not clear storage when false' do
139
+ @session = Capybara::Session.new(:selenium_firefox_not_clear_storage, TestApp)
140
140
  @session.visit('/with_js')
141
141
  @session.find(:css, '#set-storage').click
142
142
  @session.reset!
143
143
  @session.visit('/with_js')
144
- expect(@session.driver.browser.local_storage.keys).to be_empty
145
- expect(@session.driver.browser.session_storage.keys).to be_empty
144
+ expect(@session.driver.browser.local_storage.keys).not_to be_empty
145
+ expect(@session.driver.browser.session_storage.keys).not_to be_empty
146
146
  end
147
147
  end
148
148
  end
149
+
150
+ context 'timeout' do
151
+ it 'sets the http client read timeout' do
152
+ expect(TestSessions::SeleniumFirefox.driver.browser.send(:bridge).http.read_timeout).to eq 31
153
+ end
154
+ end
149
155
  end
150
156
 
151
157
  RSpec.describe Capybara::Selenium::Node do
152
158
  context '#click' do
153
159
  it 'warns when attempting on a table row' do
154
- session = TestSessions::SeleniumMarionette
160
+ session = TestSessions::SeleniumFirefox
155
161
  session.visit('/tables')
156
162
  tr = session.find(:css, '#agent_table tr:first-child')
157
163
  allow(tr.base).to receive(:warn)
@@ -160,7 +166,7 @@ RSpec.describe Capybara::Selenium::Node do
160
166
  end
161
167
 
162
168
  it 'should allow multiple modifiers', requires: [:js] do
163
- session = TestSessions::SeleniumMarionette
169
+ session = TestSessions::SeleniumFirefox
164
170
  session.visit('with_js')
165
171
  # Firefox v62+ doesn't generate an event for control+shift+click
166
172
  session.find(:css, '#click-test').click(:alt, :ctrl, :meta)
@@ -171,7 +177,7 @@ RSpec.describe Capybara::Selenium::Node do
171
177
 
172
178
  context '#send_keys' do
173
179
  it 'should process space' do
174
- session = TestSessions::SeleniumMarionette
180
+ session = TestSessions::SeleniumFirefox
175
181
  session.visit('/form')
176
182
  session.find(:css, '#address1_city').send_keys('ocean', [:shift, :space, 'side'])
177
183
  expect(session.find(:css, '#address1_city').value).to eq 'ocean SIDE'
@@ -56,25 +56,23 @@ TestSessions::RemoteFirefox.driver.browser.file_detector = lambda do |args|
56
56
  end
57
57
 
58
58
  skipped_tests = %i[response_headers status_code trigger download]
59
- # skip window tests when headless for now - closing a window not supported by chromedriver/chrome
60
- skipped_tests << :windows if ENV['TRAVIS'] && (ENV['SKIP_WINDOW'] || ENV['HEADLESS'])
61
59
 
62
60
  Capybara::SpecHelper.run_specs TestSessions::RemoteFirefox, FIREFOX_REMOTE_DRIVER.to_s, capybara_skip: skipped_tests do |example|
63
61
  case example.metadata[:full_description]
64
62
  when 'Capybara::Session selenium_firefox_remote node #click should allow multiple modifiers'
65
- skip "Firefox doesn't generate an event for shift+control+click" if marionette_gte?(62, @session)
63
+ skip "Firefox doesn't generate an event for shift+control+click" if firefox_gte?(62, @session)
66
64
  when 'Capybara::Session selenium_firefox_remote #accept_prompt should accept the prompt with a blank response when there is a default'
67
- pending "Geckodriver doesn't set a blank response in FF < 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1486485" if marionette_lt?(63, @session)
65
+ pending "Geckodriver doesn't set a blank response in FF < 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1486485" if firefox_lt?(63, @session)
68
66
  when 'Capybara::Session selenium_firefox_remote #attach_file with multipart form should fire change once for each set of files uploaded'
69
67
  pending 'Gekcodriver appends files so we have to first call clear for multiple files which creates an extra change ' \
70
68
  'if files are already set'
71
69
  when 'Capybara::Session selenium_firefox_remote #attach_file with multipart form should fire change once when uploading multiple files from empty'
72
- pending "FF < 62 doesn't support setting all files at once" if marionette_lt?(62, @session)
70
+ pending "FF < 62 doesn't support setting all files at once" if firefox_lt?(62, @session)
73
71
  when 'Capybara::Session selenium_firefox_remote #reset_session! removes ALL cookies'
74
72
  pending "Geckodriver doesn't provide a way to remove cookies outside the current domain"
75
73
  when /#accept_confirm should work with nested modals$/
76
74
  # skip because this is timing based and hence flaky when set to pending
77
- skip 'Broken in FF 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if marionette_gte?(63, @session)
75
+ skip 'Broken in FF 63 - https://bugzilla.mozilla.org/show_bug.cgi?id=1487358' if firefox_gte?(63, @session)
78
76
  end
79
77
  end
80
78
 
@@ -295,7 +295,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
295
295
 
296
296
  describe 'Element#drag_to' do
297
297
  before do
298
- skip "Firefox < 62 doesn't support a DataTransfer constuctor" if marionette_lt?(62.0, session)
298
+ skip "Firefox < 62 doesn't support a DataTransfer constuctor" if firefox_lt?(62.0, session)
299
299
  skip "IE doesn't support a DataTransfer constuctor" if ie?(session)
300
300
  end
301
301
 
@@ -336,7 +336,7 @@ RSpec.shared_examples 'Capybara::Session' do |session, mode|
336
336
 
337
337
  describe 'Capybara#Node#attach_file' do
338
338
  it 'can attach a directory' do
339
- pending "Geckodriver doesn't support uploading a directory" if marionette?(session)
339
+ pending "Geckodriver doesn't support uploading a directory" if firefox?(session)
340
340
  pending "Selenium remote doesn't support transferring a directory" if remote?(session)
341
341
  pending "Headless Chrome doesn't support directory upload - https://bugs.chromium.org/p/chromedriver/issues/detail?id=2521&q=directory%20upload&colspec=ID%20Status%20Pri%20Owner%20Summary" if chrome?(session) && ENV['HEADLESS']
342
342
 
@@ -6,17 +6,17 @@ require 'webdrivers' if ENV['CI']
6
6
 
7
7
  module Capybara
8
8
  module SpecHelper
9
- def marionette?(session)
9
+ def firefox?(session)
10
10
  browser_name(session) == :firefox &&
11
11
  session.driver.browser.capabilities.is_a?(::Selenium::WebDriver::Remote::W3C::Capabilities)
12
12
  end
13
13
 
14
- def marionette_lt?(version, session)
15
- marionette?(session) && (session.driver.browser.capabilities[:browser_version].to_f < version)
14
+ def firefox_lt?(version, session)
15
+ firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f < version)
16
16
  end
17
17
 
18
- def marionette_gte?(version, session)
19
- marionette?(session) && (session.driver.browser.capabilities[:browser_version].to_f >= version)
18
+ def firefox_gte?(version, session)
19
+ firefox?(session) && (session.driver.browser.capabilities[:browser_version].to_f >= version)
20
20
  end
21
21
 
22
22
  def chrome?(session)
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Capybara::Selector::XPathBuilder do
6
+ let :builder do
7
+ ::Capybara::Selector::XPathBuilder.new(@xpath)
8
+ end
9
+
10
+ context 'add_attribute_conditions' do
11
+ it 'adds a single string condition to a single selector' do
12
+ @xpath = './/div'
13
+ selector = builder.add_attribute_conditions(random: 'abc')
14
+ expect(selector).to eq %((.//div)[(./@random = 'abc')])
15
+ end
16
+
17
+ it 'adds multiple string conditions to a single selector' do
18
+ @xpath = './/div'
19
+ selector = builder.add_attribute_conditions(random: 'abc', other: 'def')
20
+ expect(selector).to eq %(((.//div)[(./@random = 'abc')])[(./@other = 'def')])
21
+ end
22
+
23
+ it 'adds a single string condition to a multiple selector' do
24
+ @xpath = XPath.descendant(:div, :ul)
25
+ selector = builder.add_attribute_conditions(random: 'abc')
26
+ expect(selector.to_s).to eq @xpath[XPath.attr(:random) == 'abc'].to_s
27
+ end
28
+
29
+ it 'adds multiple string conditions to a multiple selector' do
30
+ @xpath = XPath.descendant(:div, :ul)
31
+ selector = builder.add_attribute_conditions(random: 'abc', other: 'def')
32
+ expect(selector.to_s).to eq %(.//*[self::div | self::ul][(./@random = 'abc')][(./@other = 'def')])
33
+ end
34
+
35
+ it 'adds simple regexp conditions to a single selector' do
36
+ @xpath = XPath.descendant(:div)
37
+ selector = builder.add_attribute_conditions(random: /abc/, other: /def/)
38
+ expect(selector.to_s).to eq %(.//div[./@random[contains(., 'abc')]][./@other[contains(., 'def')]])
39
+ end
40
+
41
+ it 'adds wildcard regexp conditions to a single selector' do
42
+ @xpath = './/div'
43
+ selector = builder.add_attribute_conditions(random: /abc.*def/, other: /def.*ghi/)
44
+ expect(selector).to eq %(((.//div)[./@random[(contains(., 'abc') and contains(., 'def'))]])[./@other[(contains(., 'def') and contains(., 'ghi'))]])
45
+ end
46
+
47
+ it 'adds alternated regexp conditions to a single selector' do
48
+ @xpath = XPath.descendant(:div)
49
+ selector = builder.add_attribute_conditions(random: /abc|def/, other: /def|ghi/)
50
+ expect(selector.to_s).to eq %(.//div[./@random[(contains(., 'abc') or contains(., 'def'))]][./@other[(contains(., 'def') or contains(., 'ghi'))]])
51
+ end
52
+
53
+ it 'adds alternated regexp conditions to a multiple selector' do
54
+ @xpath = XPath.descendant(:div, :ul)
55
+ selector = builder.add_attribute_conditions(other: /def.*ghi|jkl/)
56
+ expect(selector.to_s).to eq %(.//*[self::div | self::ul][./@other[((contains(., 'def') and contains(., 'ghi')) or contains(., 'jkl'))]])
57
+ end
58
+
59
+ it "returns original selector when regexp can't be substringed" do
60
+ @xpath = './/div'
61
+ selector = builder.add_attribute_conditions(other: /.+/)
62
+ expect(selector).to eq '(.//div)[./@other]'
63
+ end
64
+
65
+ context ':class' do
66
+ it 'handles string' do
67
+ @xpath = './/a'
68
+ selector = builder.add_attribute_conditions(class: 'my_class')
69
+ expect(selector).to eq %((.//a)[contains(concat(' ', normalize-space(./@class), ' '), ' my_class ')])
70
+ end
71
+
72
+ it 'handles negated strings' do
73
+ @xpath = XPath.descendant(:a)
74
+ selector = builder.add_attribute_conditions(class: '!my_class')
75
+ expect(selector.to_s).to eq @xpath[!XPath.attr(:class).contains_word('my_class')].to_s
76
+ end
77
+
78
+ it 'handles array of strings' do
79
+ @xpath = './/a'
80
+ selector = builder.add_attribute_conditions(class: %w[my_class my_other_class])
81
+ expect(selector).to eq %((.//a)[(contains(concat(' ', normalize-space(./@class), ' '), ' my_class ') and contains(concat(' ', normalize-space(./@class), ' '), ' my_other_class '))])
82
+ end
83
+
84
+ it 'handles array of string when negated included' do
85
+ @xpath = XPath.descendant(:a)
86
+ selector = builder.add_attribute_conditions(class: %w[my_class !my_other_class])
87
+ expect(selector.to_s).to eq @xpath[XPath.attr(:class).contains_word('my_class') & !XPath.attr(:class).contains_word('my_other_class')].to_s
88
+ end
89
+ end
90
+ end
91
+ end