watir 6.14.0 → 6.15.0

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.
Files changed (133) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +24 -6
  3. data/CHANGES.md +10 -0
  4. data/Gemfile +0 -2
  5. data/README.md +1 -1
  6. data/Rakefile +2 -2
  7. data/lib/watir.rb +2 -3
  8. data/lib/watir/adjacent.rb +2 -2
  9. data/lib/watir/alert.rb +5 -9
  10. data/lib/watir/attribute_helper.rb +2 -3
  11. data/lib/watir/browser.rb +5 -17
  12. data/lib/watir/capabilities.rb +11 -0
  13. data/lib/watir/cell_container.rb +2 -2
  14. data/lib/watir/container.rb +7 -8
  15. data/lib/watir/cookies.rb +2 -12
  16. data/lib/watir/element_collection.rb +2 -2
  17. data/lib/watir/elements/button.rb +2 -11
  18. data/lib/watir/elements/element.rb +43 -25
  19. data/lib/watir/elements/hidden.rb +1 -1
  20. data/lib/watir/elements/iframe.rb +7 -5
  21. data/lib/watir/elements/option.rb +2 -13
  22. data/lib/watir/elements/select.rb +6 -22
  23. data/lib/watir/elements/table.rb +2 -2
  24. data/lib/watir/exception.rb +1 -2
  25. data/lib/watir/generator/base/idl_sorter.rb +1 -1
  26. data/lib/watir/generator/base/spec_extractor.rb +2 -2
  27. data/lib/watir/generator/base/visitor.rb +1 -1
  28. data/lib/watir/generator/html/generator.rb +0 -1
  29. data/lib/watir/generator/html/spec_extractor.rb +1 -1
  30. data/lib/watir/generator/html/visitor.rb +1 -1
  31. data/lib/watir/generator/svg/spec_extractor.rb +1 -1
  32. data/lib/watir/generator/svg/visitor.rb +1 -1
  33. data/lib/watir/js_execution.rb +11 -0
  34. data/lib/watir/js_snippets.rb +1 -1
  35. data/lib/watir/js_snippets/elementObscured.js +14 -0
  36. data/lib/watir/js_snippets/selectedText.js +17 -0
  37. data/lib/watir/legacy_wait.rb +5 -5
  38. data/lib/watir/locators.rb +16 -5
  39. data/lib/watir/locators/anchor/selector_builder.rb +38 -0
  40. data/lib/watir/locators/button/locator.rb +14 -12
  41. data/lib/watir/locators/button/selector_builder.rb +0 -22
  42. data/lib/watir/locators/button/selector_builder/xpath.rb +67 -12
  43. data/lib/watir/locators/button/validator.rb +2 -1
  44. data/lib/watir/locators/cell/selector_builder.rb +0 -14
  45. data/lib/watir/locators/cell/selector_builder/xpath.rb +21 -0
  46. data/lib/watir/locators/element/locator.rb +60 -153
  47. data/lib/watir/locators/element/selector_builder.rb +103 -84
  48. data/lib/watir/locators/element/selector_builder/regexp_disassembler.rb +66 -0
  49. data/lib/watir/locators/element/selector_builder/xpath.rb +195 -82
  50. data/lib/watir/locators/element/selector_builder/xpath_support.rb +27 -0
  51. data/lib/watir/locators/element/validator.rb +2 -9
  52. data/lib/watir/locators/row/selector_builder.rb +5 -22
  53. data/lib/watir/locators/row/selector_builder/xpath.rb +53 -0
  54. data/lib/watir/locators/text_area/selector_builder.rb +1 -1
  55. data/lib/watir/locators/text_area/selector_builder/xpath.rb +19 -0
  56. data/lib/watir/locators/text_field/locator.rb +11 -8
  57. data/lib/watir/locators/text_field/selector_builder.rb +0 -23
  58. data/lib/watir/locators/text_field/selector_builder/xpath.rb +33 -4
  59. data/lib/watir/locators/text_field/validator.rb +4 -4
  60. data/lib/watir/radio_set.rb +5 -5
  61. data/lib/watir/row_container.rb +2 -2
  62. data/lib/watir/user_editable.rb +2 -2
  63. data/lib/watir/version.rb +1 -1
  64. data/lib/watir/wait.rb +24 -37
  65. data/lib/watir/window.rb +11 -8
  66. data/lib/watirspec/remote_server.rb +3 -1
  67. data/spec/locator_spec_helper.rb +1 -1
  68. data/spec/spec_helper.rb +25 -1
  69. data/spec/unit/anchor_locator_spec.rb +68 -0
  70. data/spec/unit/capabilities_spec.rb +27 -0
  71. data/spec/unit/element_locator_spec.rb +184 -101
  72. data/spec/unit/logger_spec.rb +5 -0
  73. data/spec/watirspec/adjacent_spec.rb +34 -34
  74. data/spec/watirspec/after_hooks_spec.rb +78 -35
  75. data/spec/watirspec/alert_spec.rb +10 -0
  76. data/spec/watirspec/browser_spec.rb +27 -1
  77. data/spec/watirspec/element_hidden_spec.rb +6 -0
  78. data/spec/watirspec/elements/button_spec.rb +5 -11
  79. data/spec/watirspec/elements/buttons_spec.rb +1 -1
  80. data/spec/watirspec/elements/checkbox_spec.rb +2 -15
  81. data/spec/watirspec/elements/date_time_field_spec.rb +6 -1
  82. data/spec/watirspec/elements/dd_spec.rb +0 -17
  83. data/spec/watirspec/elements/del_spec.rb +0 -14
  84. data/spec/watirspec/elements/div_spec.rb +0 -18
  85. data/spec/watirspec/elements/dl_spec.rb +0 -17
  86. data/spec/watirspec/elements/dt_spec.rb +0 -17
  87. data/spec/watirspec/elements/element_spec.rb +177 -17
  88. data/spec/watirspec/elements/elements_spec.rb +7 -6
  89. data/spec/watirspec/elements/em_spec.rb +0 -13
  90. data/spec/watirspec/elements/filefield_spec.rb +0 -11
  91. data/spec/watirspec/elements/form_spec.rb +6 -0
  92. data/spec/watirspec/elements/hn_spec.rb +0 -14
  93. data/spec/watirspec/elements/iframe_spec.rb +15 -0
  94. data/spec/watirspec/elements/ins_spec.rb +0 -14
  95. data/spec/watirspec/elements/labels_spec.rb +1 -1
  96. data/spec/watirspec/elements/li_spec.rb +0 -14
  97. data/spec/watirspec/elements/link_spec.rb +22 -14
  98. data/spec/watirspec/elements/links_spec.rb +13 -0
  99. data/spec/watirspec/elements/list_spec.rb +15 -0
  100. data/spec/watirspec/elements/ol_spec.rb +0 -14
  101. data/spec/watirspec/elements/option_spec.rb +0 -10
  102. data/spec/watirspec/elements/p_spec.rb +0 -14
  103. data/spec/watirspec/elements/pre_spec.rb +0 -14
  104. data/spec/watirspec/elements/radio_spec.rb +0 -14
  105. data/spec/watirspec/elements/select_list_spec.rb +0 -10
  106. data/spec/watirspec/elements/span_spec.rb +4 -15
  107. data/spec/watirspec/elements/strong_spec.rb +4 -15
  108. data/spec/watirspec/elements/table_nesting_spec.rb +1 -1
  109. data/spec/watirspec/elements/table_spec.rb +7 -0
  110. data/spec/watirspec/elements/text_field_spec.rb +10 -2
  111. data/spec/watirspec/elements/text_fields_spec.rb +1 -1
  112. data/spec/watirspec/elements/tr_spec.rb +1 -1
  113. data/spec/watirspec/elements/ul_spec.rb +0 -14
  114. data/spec/watirspec/html/closeable.html +8 -0
  115. data/spec/watirspec/html/forms_with_input_elements.html +28 -23
  116. data/spec/watirspec/html/nested_elements.html +9 -9
  117. data/spec/watirspec/html/obscured.html +34 -0
  118. data/spec/watirspec/html/tables.html +13 -13
  119. data/spec/watirspec/radio_set_spec.rb +5 -0
  120. data/spec/watirspec/selector_builder/button_spec.rb +254 -0
  121. data/spec/watirspec/selector_builder/cell_spec.rb +93 -0
  122. data/spec/watirspec/selector_builder/element_spec.rb +639 -0
  123. data/spec/watirspec/selector_builder/row_spec.rb +150 -0
  124. data/spec/watirspec/selector_builder/text_spec.rb +170 -0
  125. data/spec/watirspec/support/rspec_matchers.rb +6 -1
  126. data/spec/watirspec/user_editable_spec.rb +4 -0
  127. data/spec/watirspec/wait_spec.rb +65 -14
  128. data/spec/watirspec/window_switching_spec.rb +54 -1
  129. data/spec/watirspec_helper.rb +2 -0
  130. data/watir.gemspec +7 -1
  131. metadata +86 -8
  132. data/lib/watir/locators/text_area/locator.rb +0 -13
  133. data/lib/watir/xpath_support.rb +0 -18
@@ -2,6 +2,9 @@ module Watir
2
2
  class Window
3
3
  include EventuallyPresent
4
4
  include Waitable
5
+ include Exception
6
+
7
+ attr_reader :browser
5
8
 
6
9
  def initialize(browser, selector)
7
10
  @browser = browser
@@ -15,7 +18,7 @@ module Watir
15
18
  else
16
19
  return if selector.keys.all? { |k| %i[title url index].include? k }
17
20
 
18
- raise ArgumentError, "invalid window selector: #{selector.inspect}"
21
+ raise ArgumentError, "invalid window selector: #{selector_string}"
19
22
  end
20
23
  end
21
24
 
@@ -99,7 +102,7 @@ module Watir
99
102
  def exists?
100
103
  assert_exists
101
104
  true
102
- rescue Exception::NoMatchingWindowFoundException
105
+ rescue NoMatchingWindowFoundException
103
106
  false
104
107
  end
105
108
 
@@ -194,8 +197,6 @@ module Watir
194
197
  @selector.inspect
195
198
  end
196
199
 
197
- protected
198
-
199
200
  def handle
200
201
  @handle ||= locate
201
202
  end
@@ -213,7 +214,9 @@ module Watir
213
214
  end
214
215
 
215
216
  def assert_exists
216
- raise(Exception::NoMatchingWindowFoundException, @selector.inspect) unless @driver.window_handles.include?(handle)
217
+ return if @driver.window_handles.include?(handle)
218
+
219
+ raise(NoMatchingWindowFoundException, selector_string)
217
220
  end
218
221
 
219
222
  # return a handle to the currently active window if it is still open; otherwise nil
@@ -225,8 +228,8 @@ module Watir
225
228
 
226
229
  def matches?(handle)
227
230
  @driver.switch_to.window(handle) do
228
- matches_title = @selector[:title].nil? || @driver.title =~ /#{@selector[:title]}/
229
- matches_url = @selector[:url].nil? || @driver.current_url =~ /#{@selector[:url]}/
231
+ matches_title = @selector[:title].nil? || @browser.title =~ /#{@selector[:title]}/
232
+ matches_url = @selector[:url].nil? || @browser.url =~ /#{@selector[:url]}/
230
233
 
231
234
  matches_title && matches_url
232
235
  end
@@ -241,7 +244,7 @@ module Watir
241
244
  begin
242
245
  wait_until(&:exists?)
243
246
  rescue Wait::TimeoutError
244
- raise Exception::NoMatchingWindowFoundException, @selector.inspect
247
+ raise NoMatchingWindowFoundException, selector_string
245
248
  end
246
249
  end
247
250
  end # Window
@@ -1,5 +1,7 @@
1
1
  module WatirSpec
2
2
  class RemoteServer
3
+ include Watir::Exception
4
+
3
5
  attr_reader :server
4
6
 
5
7
  def start(port = 4444, args: [])
@@ -33,7 +35,7 @@ module WatirSpec
33
35
  end
34
36
  rescue SocketError
35
37
  # not connected to internet
36
- raise Watir::Exception::Error, 'unable to find or download selenium-server-standalone jar'
38
+ raise Error, 'unable to find or download selenium-server-standalone jar'
37
39
  end
38
40
  end
39
41
  end
@@ -10,7 +10,7 @@ module LocatorSpecHelper
10
10
  def locator(selector, attrs)
11
11
  attrs ||= Watir::HTMLElement.attributes
12
12
  element_validator = Watir::Locators::Element::Validator.new
13
- selector_builder = Watir::Locators::Element::SelectorBuilder.new(driver, selector, attrs)
13
+ selector_builder = Watir::Locators::Element::SelectorBuilder.new(attrs)
14
14
  Watir::Locators::Element::Locator.new(browser, selector, selector_builder, element_validator)
15
15
  end
16
16
 
@@ -2,13 +2,37 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
3
 
4
4
  require 'coveralls'
5
- Coveralls.wear!
5
+ require 'simplecov'
6
+ require 'simplecov-console'
7
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [
8
+ Coveralls::SimpleCov::Formatter,
9
+ SimpleCov::Formatter::HTMLFormatter,
10
+ SimpleCov::Formatter::Console
11
+ ]
12
+ SimpleCov.start do
13
+ add_filter %r{/spec/}
14
+ add_filter 'lib/watir/elements/html_elements.rb'
15
+ add_filter 'lib/watir/elements/svg_elements.rb'
16
+ add_filter 'lib/watir/legacy_wait.rb'
17
+ add_filter 'lib/watirspec'
18
+ refuse_coverage_drop
19
+ end
6
20
 
7
21
  require 'watir'
8
22
  require 'webdrivers'
9
23
  require 'locator_spec_helper'
10
24
  require 'rspec'
11
25
 
26
+ if ENV['TRAVIS']
27
+ require 'rspec/retry'
28
+ RSpec.configure do |config|
29
+ config.verbose_retry = true
30
+ config.display_try_failure_messages = true
31
+ config.default_retry_count = 3
32
+ config.exceptions_to_retry = [IOError, Net::ReadTimeout]
33
+ end
34
+ end
35
+
12
36
  SELENIUM_SELECTORS = %i[css tag_name xpath link_text partial_link_text link].freeze
13
37
 
14
38
  Watir.relaxed_locate = false if ENV['RELAXED_LOCATE'] == 'false'
@@ -0,0 +1,68 @@
1
+ require_relative 'unit_helper'
2
+
3
+ describe Watir::Locators::Element::Locator do
4
+ include LocatorSpecHelper
5
+
6
+ def locator(selector, attrs)
7
+ attrs ||= Watir::HTMLElement.attributes
8
+ element_validator = Watir::Locators::Element::Validator.new
9
+ selector_builder = Watir::Locators::Anchor::SelectorBuilder.new(attrs)
10
+ Watir::Locators::Element::Locator.new(browser, selector, selector_builder, element_validator)
11
+ end
12
+
13
+ it 'converts :visible_text with String to :link_text' do
14
+ link = element(tag_name: 'a')
15
+ expect_one(:link_text, 'exact text').and_return(link)
16
+ expect(link).not_to receive(:text) # matching does not validate text
17
+
18
+ locate_one(tag_name: 'a', visible_text: 'exact text')
19
+ end
20
+
21
+ it 'converts :visible_text with basic Regexp to :partial_link_text' do
22
+ link = element(tag_name: 'a')
23
+ expect_one(:partial_link_text, 'partial text').and_return(link)
24
+ expect(link).not_to receive(:text) # matching does not validate text
25
+
26
+ locate_one(tag_name: 'a', visible_text: /partial text/)
27
+ end
28
+
29
+ it 'does not convert :visible_text with complex Regexp' do
30
+ elements = [
31
+ element(tag_name: 'a', text: 'other text'),
32
+ element(tag_name: 'a', text: 'matching complex!text')
33
+ ]
34
+ expect_all(:xpath, ".//*[local-name()='a']").and_return(elements)
35
+
36
+ expect(locate_one(tag_name: 'a', visible_text: /complex.text/)).to eq(elements[1])
37
+ end
38
+
39
+ it 'does not convert :visible_text with casefold Regexp' do
40
+ elements = [
41
+ element(tag_name: 'a', text: 'other text'),
42
+ element(tag_name: 'a', text: 'partial text')
43
+ ]
44
+ expect_all(:xpath, ".//*[local-name()='a']").and_return(elements)
45
+
46
+ expect(locate_one(tag_name: 'a', visible_text: /partial text/i)).to eq(elements[1])
47
+ end
48
+
49
+ it 'does not convert :visible_text with String and other locators' do
50
+ els = [
51
+ element(tag_name: 'a', attributes: {class: 'klass', name: 'abc'}, text: 'other text'),
52
+ element(tag_name: 'a', attributes: {class: 'klass', name: 'abc'}, text: 'exact text')
53
+ ]
54
+ expect_all(:xpath, ".//*[local-name()='a'][contains(concat(' ', @class, ' '), ' klass ')][@name]").and_return(els)
55
+
56
+ expect(locate_one(tag_name: 'a', class: 'klass', name: /a|.c/, visible_text: 'exact text')).to eq(els[1])
57
+ end
58
+
59
+ it 'does not convert :visible_text with Regexp and other locators' do
60
+ els = [
61
+ element(tag_name: 'a', attributes: {class: 'klass', name: 'abc'}, text: 'other text'),
62
+ element(tag_name: 'a', attributes: {class: 'klass', name: 'abc'}, text: 'partial text')
63
+ ]
64
+ expect_all(:xpath, ".//*[local-name()='a'][contains(concat(' ', @class, ' '), ' klass ')][@name]").and_return(els)
65
+
66
+ expect(locate_one(tag_name: 'a', class: 'klass', name: /a|.c/, visible_text: /partial text/)).to eq(els[1])
67
+ end
68
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'unit_helper'
2
+
3
+ describe Watir::Capabilities do
4
+ describe '#ie' do
5
+ it 'processes options' do
6
+ options = {browser_attach_timeout: 1, full_page_screenshot: true}
7
+ caps = Watir::Capabilities.new(:ie, options: options)
8
+ opts = caps.to_args.last[:options]
9
+ expect(opts.browser_attach_timeout).to eq 1
10
+ expect(opts.full_page_screenshot).to be true
11
+ end
12
+
13
+ it 'processes args' do
14
+ caps = Watir::Capabilities.new(:ie, args: %w[foo bar])
15
+ opts = caps.to_args.last[:options]
16
+ expect(opts.args).to eq Set.new(%w[foo bar])
17
+ end
18
+
19
+ it 'processes options class' do
20
+ options = Selenium::WebDriver::IE::Options.new(browser_attach_timeout: 1, full_page_screenshot: true)
21
+ caps = Watir::Capabilities.new(:ie, options: options)
22
+ opts = caps.to_args.last[:options]
23
+ expect(opts.browser_attach_timeout).to eq 1
24
+ expect(opts.full_page_screenshot).to be true
25
+ end
26
+ end
27
+ end
@@ -13,6 +13,14 @@ describe Watir::Locators::Element::Locator do
13
13
  expect { locate_one loc => 'bar' }.send(match, output(msg).to_stdout_from_any_process)
14
14
  end
15
15
  end
16
+
17
+ it 'raises exception if locating a non-link element by link locator' do
18
+ selector = {tag_name: 'div', link_text: 'foo'}
19
+ msg = 'Can not use link_text locator to find a foo element'
20
+ expect {
21
+ expect { locate_one(selector) }.to raise_exception(StandardError, msg)
22
+ }.to have_deprecated_link_text
23
+ end
16
24
  end
17
25
 
18
26
  describe 'with selectors not supported by Selenium' do
@@ -69,7 +77,7 @@ describe Watir::Locators::Element::Locator do
69
77
  end
70
78
 
71
79
  it 'handles selector with multiple classes in array' do
72
- xpath = ".//*[(contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b '))]"
80
+ xpath = ".//*[contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b ')]"
73
81
  expect_one :xpath, xpath
74
82
 
75
83
  locate_one class: %w[a b]
@@ -81,7 +89,24 @@ describe Watir::Locators::Element::Locator do
81
89
  expect { locate_one class: 'a b' }.to have_deprecated_class_array
82
90
  end
83
91
 
84
- it 'handles selector with tag_name and xpath' do
92
+ it 'handles selector with xpath and tag_name String' do
93
+ elements = [
94
+ element(tag_name: 'div', attributes: {class: 'foo'}),
95
+ element(tag_name: 'span', attributes: {class: 'foo'}),
96
+ element(tag_name: 'div', attributes: {class: 'foo'})
97
+ ]
98
+
99
+ expect_all(:xpath, './/*[@class="foo"]').and_return(elements)
100
+
101
+ selector = {
102
+ xpath: './/*[@class="foo"]',
103
+ tag_name: 'span'
104
+ }
105
+
106
+ expect(locate_one(selector).tag_name).to eq 'span'
107
+ end
108
+
109
+ it 'handles selector with xpath and tag_name Symbol' do
85
110
  elements = [
86
111
  element(tag_name: 'div', attributes: {class: 'foo'}),
87
112
  element(tag_name: 'span', attributes: {class: 'foo'}),
@@ -123,6 +148,7 @@ describe Watir::Locators::Element::Locator do
123
148
  text: 'foo'
124
149
  end
125
150
 
151
+ # TODO: This is deprecated by 'text_string'
126
152
  it "handles 'text' key when it's a string" do
127
153
  expect_one :xpath, ".//*[local-name()='div'][normalize-space()='foo']"
128
154
  locate_one tag_name: 'div',
@@ -169,7 +195,8 @@ describe Watir::Locators::Element::Locator do
169
195
  end
170
196
 
171
197
  it 'wraps :type attribute with translate() for upper case values' do
172
- translated_type = "translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')"
198
+ translated_type = "translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
199
+ "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ')"
173
200
  expect_one :xpath, ".//*[local-name()='input'][#{translated_type}='file']"
174
201
 
175
202
  selector = [
@@ -181,7 +208,8 @@ describe Watir::Locators::Element::Locator do
181
208
  end
182
209
 
183
210
  it "uses the corresponding <label>'s @for attribute or parent::label when locating by label" do
184
- translated_type = "translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')"
211
+ translated_type = "translate(@type,'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
212
+ "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ')"
185
213
  xpath = ".//*[local-name()='input'][#{translated_type}='text' and " \
186
214
  "(@id=//label[normalize-space()='foo']/@for or " \
187
215
  "parent::label[normalize-space()='foo'])]"
@@ -212,30 +240,22 @@ describe Watir::Locators::Element::Locator do
212
240
  }
213
241
 
214
242
  locate_one selector, Watir::Meta.attributes
215
-
216
- # TODO: check edge cases
217
243
  end
218
244
  end
219
245
 
220
- describe 'with regexp selectors' do
221
- it 'handles selector with tag name and a single regexp attribute' do
222
- elements = [
223
- element(tag_name: 'div', attributes: {class: 'foo'}),
224
- element(tag_name: 'div', attributes: {class: 'foob'})
225
- ]
246
+ describe 'with simple regexp selectors' do
247
+ it 'handles selector with tag name and a simple regexp attribute' do
248
+ element = element(tag_name: 'div', attributes: {class: 'foob'})
226
249
 
227
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@class, 'oob')]").and_return(elements)
250
+ expect_one(:xpath, ".//*[local-name()='div'][contains(@class, 'oob')]").and_return(element)
228
251
 
229
- expect(locate_one(tag_name: 'div', class: /oob/)).to eq elements[1]
252
+ expect(locate_one(tag_name: 'div', class: /oob/)).to eq element
230
253
  end
231
254
 
232
- it 'handles :tag_name, :index and a single regexp attribute' do
233
- elements = [
234
- element(tag_name: 'div', attributes: {class: 'foo'}),
235
- element(tag_name: 'div', attributes: {class: 'foo'})
236
- ]
255
+ it 'handles :tag_name, :index and a simple regexp attribute' do
256
+ element = element(tag_name: 'div', attributes: {class: 'foo'})
237
257
 
238
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@class, 'foo')]").and_return(elements)
258
+ expect_one(:xpath, "(.//*[local-name()='div'][contains(@class, 'foo')])[2]").and_return(element)
239
259
 
240
260
  selector = {
241
261
  tag_name: 'div',
@@ -243,7 +263,7 @@ describe Watir::Locators::Element::Locator do
243
263
  index: 1
244
264
  }
245
265
 
246
- expect(locate_one(selector)).to eq elements[1]
266
+ expect(locate_one(selector)).to eq element
247
267
  end
248
268
 
249
269
  it 'handles :xpath and :index selectors' do
@@ -279,12 +299,9 @@ describe Watir::Locators::Element::Locator do
279
299
  end
280
300
 
281
301
  it 'handles mix of string and regexp attributes' do
282
- elements = [
283
- element(tag_name: 'div', attributes: {dir: 'foo', title: 'bar'}),
284
- element(tag_name: 'div', attributes: {dir: 'foo', title: 'baz'})
285
- ]
302
+ element = element(tag_name: 'div', attributes: {dir: 'foo', title: 'baz'})
286
303
 
287
- expect_all(:xpath, "(.//*[local-name()='div'][@dir='foo'])[contains(@title, 'baz')]").and_return(elements)
304
+ expect_one(:xpath, ".//*[local-name()='div'][@dir='foo' and contains(@title, 'baz')]").and_return(element)
288
305
 
289
306
  selector = {
290
307
  tag_name: 'div',
@@ -292,23 +309,20 @@ describe Watir::Locators::Element::Locator do
292
309
  title: /baz/
293
310
  }
294
311
 
295
- expect(locate_one(selector)).to eq elements[1]
312
+ expect(locate_one(selector)).to eq element
296
313
  end
297
314
 
298
315
  it 'handles data-* attributes with regexp' do
299
- elements = [
300
- element(tag_name: 'div', attributes: {'data-automation-id': 'foo'}),
301
- element(tag_name: 'div', attributes: {'data-automation-id': 'bar'})
302
- ]
316
+ element = element(tag_name: 'div', attributes: {'data-automation-id': 'bar'})
303
317
 
304
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@data-automation-id, 'bar')]").and_return(elements)
318
+ expect_one(:xpath, ".//*[local-name()='div'][contains(@data-automation-id, 'bar')]").and_return(element)
305
319
 
306
320
  selector = {
307
321
  tag_name: 'div',
308
322
  data_automation_id: /bar/
309
323
  }
310
324
 
311
- expect(locate_one(selector)).to eq elements[1]
325
+ expect(locate_one(selector)).to eq element
312
326
  end
313
327
 
314
328
  it 'handles :label => /regexp/ selector' do
@@ -341,28 +355,28 @@ describe Watir::Locators::Element::Locator do
341
355
 
342
356
  allow(elements1.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
343
357
 
344
- expect_all(:xpath, "(.//*)[contains(@class, 'foo')]").and_return(elements1, elements2)
358
+ expect_all(:xpath, ".//*[contains(@class, 'foo')]").and_return(elements1, elements2)
345
359
 
346
- expect(locate_one(class: /foo/)).to eq elements2[0]
360
+ expect(locate_one(class: /foo$/)).to eq elements2[0]
347
361
  end
348
- end
349
362
 
350
- it 'finds all if :index is given' do
351
- # or could we use XPath indeces reliably instead?
352
- elements = [
353
- element(tag_name: 'div'),
354
- element(tag_name: 'div')
355
- ]
363
+ it 'raises error if too many attempts to relocate a stale element during filtering' do
364
+ element1 = element(tag_name: 'div', attributes: {class: 'foo'})
365
+ element2 = element(tag_name: 'div', attributes: {class: 'foob'})
356
366
 
357
- expect_all(:xpath, ".//*[local-name()='div'][@dir='foo']").and_return(elements)
367
+ elements1 = [element1.clone, element2.clone]
368
+ elements2 = [element1.clone, element2.clone]
369
+ elements3 = [element1.clone, element2.clone]
358
370
 
359
- selector = {
360
- tag_name: 'div',
361
- dir: 'foo',
362
- index: 1
363
- }
371
+ allow(elements1.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
372
+ allow(elements2.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
373
+ allow(elements3.first).to receive(:attribute).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
374
+
375
+ expect_all(:xpath, ".//*[contains(@class, 'foo')]").and_return(elements1, elements2, elements3)
364
376
 
365
- expect(locate_one(selector)).to eq elements[1]
377
+ msg = 'Unable to locate element from {:class=>[/foo$/]} due to changing page'
378
+ expect { locate_one(class: /foo$/) }.to raise_exception(Watir::Exception::LocatorException, msg)
379
+ end
366
380
  end
367
381
 
368
382
  it "returns nil if found element didn't match the selector tag_name" do
@@ -376,6 +390,17 @@ describe Watir::Locators::Element::Locator do
376
390
  expect(locate_one(selector, Watir::Input.attributes)).to be_nil
377
391
  end
378
392
 
393
+ it 'allows tag_name values to be Symbols when combined with xpath' do
394
+ expect_all(:xpath, '//div').and_return([element(tag_name: 'div')])
395
+
396
+ selector = {
397
+ tag_name: :input,
398
+ xpath: '//div'
399
+ }
400
+
401
+ expect(locate_one(selector, Watir::Input.attributes)).to be_nil
402
+ end
403
+
379
404
  describe 'errors' do
380
405
  it 'raises a TypeError if :index is not a Integer' do
381
406
  expect { locate_one(tag_name: 'div', index: 'bar') }.to \
@@ -383,10 +408,54 @@ describe Watir::Locators::Element::Locator do
383
408
  end
384
409
 
385
410
  it 'raises a TypeError if selector value is not a String, Regexp or Boolean' do
386
- num_type = RUBY_VERSION[/^\d+\.(\d+)/, 1].to_i >= 4 ? 'Integer' : 'Fixnum'
387
- msg = %(expected one of [Array, String, Regexp, TrueClass, FalseClass, Symbol], got 123:#{num_type})
388
- expect { locate_one(tag_name: 123) }
389
- .to raise_error TypeError, msg
411
+ msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 123:(Integer|Fixnum)/
412
+ expect { locate_one(foo: 123) }.to raise_error TypeError, msg
413
+ end
414
+
415
+ it 'raises a Error if selector key is not a String or a Symbol' do
416
+ msg = /Unable to build XPath using 7:(Integer|Fixnum)/
417
+ expect { locate_one(7 => 'bad') }.to raise_exception(Watir::Exception::Error, msg)
418
+ end
419
+
420
+ it 'raises a Error if selector key is not a String or a Symbol' do
421
+ msg = /Unable to build XPath using 7:(Integer|Fixnum)/
422
+ expect { locate_one(7 => 'bad') }.to raise_exception(Watir::Exception::Error, msg)
423
+ end
424
+
425
+ it 'raises an Error if unable to build selector' do
426
+ module Foo
427
+ class SelectorBuilder < Watir::Locators::Element::SelectorBuilder
428
+ def build(*_args)
429
+ nil
430
+ end
431
+ end
432
+ end
433
+
434
+ selector = {name: 'foo'}
435
+ element_validator = Watir::Locators::Element::Validator.new
436
+ selector_builder = Foo::SelectorBuilder.new(Watir::HTMLElement.attributes)
437
+ locator = Watir::Locators::Element::Locator.new(browser, selector, selector_builder, element_validator)
438
+
439
+ msg = 'Foo::SelectorBuilder was unable to build selector from {:name=>"foo"}'
440
+ expect { locator.locate }.to raise_exception(Watir::Exception::LocatorException, msg)
441
+ end
442
+
443
+ it 'raises an Error if unable to build values to match' do
444
+ module Foo
445
+ class SelectorBuilder < Watir::Locators::Element::SelectorBuilder
446
+ def build(*_args)
447
+ {}
448
+ end
449
+ end
450
+ end
451
+
452
+ selector = {name: 'foo'}
453
+ element_validator = Watir::Locators::Element::Validator.new
454
+ selector_builder = Foo::SelectorBuilder.new(Watir::HTMLElement.attributes)
455
+ locator = Watir::Locators::Element::Locator.new(browser, selector, selector_builder, element_validator)
456
+
457
+ msg = 'Foo::SelectorBuilder#build is not returning expected responses for the current version of Watir'
458
+ expect { locator.locate }.to raise_exception(Watir::Exception::LocatorException, msg)
390
459
  end
391
460
  end
392
461
  end
@@ -431,7 +500,7 @@ describe Watir::Locators::Element::Locator do
431
500
  end
432
501
 
433
502
  it 'handles selector with multiple classes in array' do
434
- xpath = ".//*[(contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b '))]"
503
+ xpath = ".//*[contains(concat(' ', @class, ' '), ' a ') and contains(concat(' ', @class, ' '), ' b ')]"
435
504
  expect_all :xpath, xpath
436
505
 
437
506
  locate_all class: %w[a b]
@@ -447,24 +516,22 @@ describe Watir::Locators::Element::Locator do
447
516
  describe 'with regexp selectors' do
448
517
  it 'handles selector with tag name and a single regexp attribute' do
449
518
  elements = [
450
- element(tag_name: 'div', attributes: {class: 'foo'}),
451
519
  element(tag_name: 'div', attributes: {class: 'foob'}),
452
520
  element(tag_name: 'div', attributes: {class: 'doob'}),
453
521
  element(tag_name: 'div', attributes: {class: 'noob'})
454
522
  ]
455
523
 
456
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@class, 'oob')]").and_return(elements)
524
+ expect_all(:xpath, ".//*[local-name()='div'][contains(@class, 'oob')]").and_return(elements)
457
525
  expect(locate_all(tag_name: 'div', class: /oob/)).to eq elements.last(3)
458
526
  end
459
527
 
460
528
  it 'handles mix of string and regexp attributes' do
461
529
  elements = [
462
- element(tag_name: 'div', attributes: {dir: 'foo', title: 'bar'}),
463
530
  element(tag_name: 'div', attributes: {dir: 'foo', title: 'baz'}),
464
531
  element(tag_name: 'div', attributes: {dir: 'foo', title: 'bazt'})
465
532
  ]
466
533
 
467
- expect_all(:xpath, "(.//*[local-name()='div'][@dir='foo'])[contains(@title, 'baz')]").and_return(elements)
534
+ expect_all(:xpath, ".//*[local-name()='div'][@dir='foo' and contains(@title, 'baz')]").and_return(elements)
468
535
 
469
536
  selector = {
470
537
  tag_name: 'div',
@@ -474,64 +541,80 @@ describe Watir::Locators::Element::Locator do
474
541
 
475
542
  expect(locate_all(selector)).to eq elements.last(2)
476
543
  end
544
+ end
477
545
 
478
- context 'and xpath' do
479
- it 'converts a leading run of regexp literals to a contains() expression' do
480
- elements = [
481
- element(tag_name: 'div', attributes: {class: 'foo'}),
482
- element(tag_name: 'div', attributes: {class: 'foob'}),
483
- element(tag_name: 'div', attributes: {class: 'bar'})
484
- ]
546
+ it 'with :index' do
547
+ element = element(tag_name: 'div')
485
548
 
486
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@class, 'fo')]").and_return(elements.first(2))
549
+ expect_one(:xpath, "(.//*[local-name()='div'][@dir='foo'])[2]").and_return(element)
487
550
 
488
- expect(locate_one(tag_name: 'div', class: /fo.b$/)).to eq elements[1]
489
- end
551
+ selector = {
552
+ tag_name: 'div',
553
+ dir: 'foo',
554
+ index: 1
555
+ }
490
556
 
491
- it 'converts a trailing run of regexp literals to a contains() expression' do
492
- elements = [
493
- element(tag_name: 'div', attributes: {class: 'foo'}),
494
- element(tag_name: 'div', attributes: {class: 'foob'})
495
- ]
557
+ expect(locate_one(selector)).to eq element
558
+ end
496
559
 
497
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@class, 'b')]").and_return(elements.last(1))
560
+ context 'and xpath' do
561
+ it 'converts a leading run of regexp literals to a contains() expression' do
562
+ elements = [
563
+ element(tag_name: 'div', attributes: {foo: 'foo'}),
564
+ element(tag_name: 'div', attributes: {foo: 'foob'}),
565
+ element(tag_name: 'div', attributes: {foo: 'bar'})
566
+ ]
498
567
 
499
- expect(locate_one(tag_name: 'div', class: /^fo.b/)).to eq elements[1]
500
- end
568
+ expect_all(:xpath, ".//*[local-name()='div'][contains(@foo, 'fo') and contains(@foo, 'b')]")
569
+ .and_return(elements.first(2))
501
570
 
502
- it 'converts a leading and a trailing run of regexp literals to a contains() expression' do
503
- elements = [
504
- element(tag_name: 'div', attributes: {class: 'foo'}),
505
- element(tag_name: 'div', attributes: {class: 'foob'})
506
- ]
571
+ expect(locate_one(tag_name: 'div', foo: /fo.b$/)).to eq elements[1]
572
+ end
507
573
 
508
- expect_all(:xpath, "(.//*[local-name()='div'])[contains(@class, 'fo') and contains(@class, 'b')]")
509
- .and_return(elements.last(1))
574
+ it 'converts a trailing run of regexp literals to a contains() expression' do
575
+ elements = [
576
+ element(tag_name: 'div', attributes: {foo: 'foo'}),
577
+ element(tag_name: 'div', attributes: {foo: 'foob'})
578
+ ]
510
579
 
511
- expect(locate_one(tag_name: 'div', class: /fo.b/)).to eq elements[1]
512
- end
580
+ expect_all(:xpath, ".//*[local-name()='div'][contains(@foo, 'fo') and contains(@foo, 'b')]")
581
+ .and_return(elements.last(1))
513
582
 
514
- it 'does not try to convert case insensitive expressions' do
515
- elements = [
516
- element(tag_name: 'div', attributes: {class: 'foo'}),
517
- element(tag_name: 'div', attributes: {class: 'foob'})
518
- ]
583
+ expect(locate_one(tag_name: 'div', foo: /^fo.b/)).to eq elements[1]
584
+ end
519
585
 
520
- expect_all(:xpath, ".//*[local-name()='div']").and_return(elements.last(1))
586
+ it 'converts a leading and a trailing run of regexp literals to a contains() expression' do
587
+ elements = [
588
+ element(tag_name: 'div', attributes: {foo: 'foo'}),
589
+ element(tag_name: 'div', attributes: {foo: 'foob'})
590
+ ]
521
591
 
522
- expect(locate_one(tag_name: 'div', class: /FOOB/i)).to eq elements[1]
523
- end
592
+ expect_all(:xpath, ".//*[local-name()='div'][contains(@foo, 'fo') and contains(@foo, 'b')]")
593
+ .and_return(elements.last(1))
524
594
 
525
- it "does not try to convert expressions containing '|'" do
526
- elements = [
527
- element(tag_name: 'div', attributes: {class: 'foo'}),
528
- element(tag_name: 'div', attributes: {class: 'foob'})
529
- ]
595
+ expect(locate_one(tag_name: 'div', foo: /fo.b/)).to eq elements[1]
596
+ end
530
597
 
531
- expect_all(:xpath, ".//*[local-name()='div']").and_return(elements.last(1))
598
+ it 'does not try to convert case insensitive expressions' do
599
+ element = element(tag_name: 'div', attributes: {foo: 'foo'})
532
600
 
533
- expect(locate_one(tag_name: 'div', class: /x|b/)).to eq elements[1]
534
- end
601
+ xpath = ".//*[local-name()='div'][contains(translate" \
602
+ "(@foo,'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
603
+ "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'), 'foob')]"
604
+ expect_one(:xpath, xpath).and_return(element)
605
+
606
+ expect(locate_one(tag_name: 'div', foo: /FOOB/i)).to eq element
607
+ end
608
+
609
+ it "does not try to convert expressions containing '|'" do
610
+ elements = [
611
+ element(tag_name: 'div', attributes: {foo: 'foo'}),
612
+ element(tag_name: 'div', attributes: {foo: 'foob'})
613
+ ]
614
+
615
+ expect_all(:xpath, ".//*[local-name()='div'][@foo]").and_return(elements.last(1))
616
+
617
+ expect(locate_one(tag_name: 'div', foo: /x|b/)).to eq elements[1]
535
618
  end
536
619
  end
537
620