watir 6.14.0 → 6.15.0

Sign up to get free protection for your applications and to get access to all the features.
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