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
@@ -0,0 +1,93 @@
1
+ require 'watirspec_helper'
2
+
3
+ describe Watir::Locators::Cell::SelectorBuilder do
4
+ let(:attributes) { Watir::HTMLElement.attribute_list }
5
+ let(:selector_builder) { described_class.new(attributes) }
6
+
7
+ describe '#build' do
8
+ after(:each) do |example|
9
+ next if example.metadata[:skip_after]
10
+
11
+ @query_scope ||= browser.element(id: 'gregory').locate
12
+
13
+ built = selector_builder.build(@selector)
14
+ expect(built).to eq [@wd_locator, (@remaining || {})]
15
+
16
+ next unless @data_locator || @tag_name
17
+
18
+ expect { @located = @query_scope.wd.first(@wd_locator) }.not_to raise_exception
19
+
20
+ if @data_locator
21
+ expect(@located.attribute('data-locator')).to eq(@data_locator)
22
+ else
23
+ expect {
24
+ expect(@located.tag_name).to eq @tag_name
25
+ }.not_to raise_exception
26
+ end
27
+ end
28
+
29
+ it 'without any arguments' do
30
+ browser.goto(WatirSpec.url_for('tables.html'))
31
+ @selector = {}
32
+ @wd_locator = {xpath: "./*[local-name()='th' or local-name()='td']"}
33
+ @data_locator = 'first cell'
34
+ end
35
+
36
+ context 'with index' do
37
+ before(:each) do
38
+ browser.goto(WatirSpec.url_for('tables.html'))
39
+ end
40
+
41
+ it 'positive' do
42
+ @selector = {index: 3}
43
+ @wd_locator = {xpath: "(./*[local-name()='th' or local-name()='td'])[4]"}
44
+ @data_locator = 'after tax'
45
+ end
46
+
47
+ it 'negative' do
48
+ @selector = {index: -3}
49
+ @wd_locator = {xpath: "(./*[local-name()='th' or local-name()='td'])[last()-2]"}
50
+ @data_locator = 'before tax'
51
+ end
52
+
53
+ it 'last' do
54
+ @selector = {index: -1}
55
+ @wd_locator = {xpath: "(./*[local-name()='th' or local-name()='td'])[last()]"}
56
+ @data_locator = 'after tax'
57
+ end
58
+
59
+ it 'does not return index if it is zero' do
60
+ @selector = {index: 0}
61
+ @wd_locator = {xpath: "./*[local-name()='th' or local-name()='td']"}
62
+ @data_locator = 'first cell'
63
+ end
64
+
65
+ it 'raises exception when index is not an Integer', skip_after: true do
66
+ selector = {index: 'foo'}
67
+ msg = 'expected Integer, got "foo":String'
68
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
69
+ end
70
+ end
71
+
72
+ context 'with multiple locators' do
73
+ before(:each) do
74
+ browser.goto(WatirSpec.url_for('tables.html'))
75
+ end
76
+
77
+ it 'attribute and text' do
78
+ @selector = {headers: /before_tax/, text: '5 934'}
79
+ @wd_locator = {xpath: "./*[local-name()='th' or local-name()='td']" \
80
+ "[normalize-space()='5 934'][contains(@headers, 'before_tax')]"}
81
+ @data_locator = 'before tax'
82
+ end
83
+ end
84
+
85
+ it 'delegates adjacent to Element SelectorBuilder' do
86
+ @query_scope = browser.element(id: 'p3').locate
87
+
88
+ @selector = {adjacent: :ancestor, index: 2}
89
+ @wd_locator = {xpath: './ancestor::*[3]'}
90
+ @data_locator = 'top table'
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,639 @@
1
+ require 'watirspec_helper'
2
+
3
+ describe Watir::Locators::Element::SelectorBuilder do
4
+ let(:selector_builder) { described_class.new(@attributes) }
5
+
6
+ describe '#build' do
7
+ after(:each) do |example|
8
+ next if example.metadata[:skip_after]
9
+
10
+ @attributes ||= Watir::HTMLElement.attribute_list
11
+ @query_scope ||= browser
12
+ built = selector_builder.build(@selector)
13
+ expect(built).to eq [@wd_locator, (@remaining || {})]
14
+
15
+ next unless @data_locator || @tag_name
16
+
17
+ expect { @located = @query_scope.wd.first(@wd_locator) }.not_to raise_exception
18
+
19
+ if @data_locator
20
+ expect(@located.attribute('data-locator')).to eq(@data_locator)
21
+ else
22
+ expect {
23
+ expect(@located.tag_name).to eq @tag_name
24
+ }.not_to raise_exception
25
+ end
26
+ end
27
+
28
+ it 'without any arguments' do
29
+ @selector = {}
30
+ @wd_locator = {xpath: './/*'}
31
+ @tag_name = 'html'
32
+ end
33
+
34
+ context 'with xpath or css' do
35
+ before(:each) do
36
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
37
+ end
38
+
39
+ it 'locates with xpath only' do
40
+ @selector = {xpath: './/div'}
41
+ @wd_locator = @selector.dup
42
+ @data_locator = 'first div'
43
+ end
44
+
45
+ it 'locates with css only' do
46
+ @selector = {css: 'div'}
47
+ @wd_locator = @selector.dup
48
+ @data_locator = 'first div'
49
+ end
50
+
51
+ it 'raises exception when using xpath & css', skip_after: true do
52
+ selector = {xpath: './/*', css: 'div'}
53
+ msg = ':xpath and :css cannot be combined ({:xpath=>".//*", :css=>"div"})'
54
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
55
+ end
56
+
57
+ it 'raises exception when combining with xpath', skip_after: true do
58
+ selector = {xpath: './/*', foo: 'div'}
59
+ msg = 'xpath cannot be combined with all of these locators ({:foo=>"div"})'
60
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
61
+ end
62
+
63
+ it 'raises exception when combining with css', skip_after: true do
64
+ selector = {css: 'div', foo: 'div'}
65
+ msg = 'css cannot be combined with all of these locators ({:foo=>"div"})'
66
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
67
+ end
68
+
69
+ it 'raises exception when not a String', skip_after: true do
70
+ selector = {xpath: 7}
71
+ msg = /expected String, got 7:(Fixnum|Integer)/
72
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
73
+ end
74
+
75
+ xcontext 'Ideal Behavior' do
76
+ it 'xpath' do
77
+ selector = {xpath: ".//*[@id='foo']", tag_name: 'div'}
78
+ expected = {xpath: ".//*[local-name()='div'][@id='foo']"}
79
+
80
+ expect_built(selector, expected)
81
+ end
82
+
83
+ it 'css' do
84
+ selector = {css: '.bar', tag_name: 'div'}
85
+ expected = {css: 'div.bar'}
86
+
87
+ expect_built(selector, expected)
88
+ end
89
+ end
90
+ end
91
+
92
+ context 'with tag_name' do
93
+ before(:each) do
94
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
95
+ end
96
+
97
+ it 'with String equals' do
98
+ @selector = {tag_name: 'div'}
99
+ @wd_locator = {xpath: ".//*[local-name()='div']"}
100
+ @data_locator = 'first div'
101
+ end
102
+
103
+ it 'with simple Regexp contains' do
104
+ @selector = {tag_name: /div/}
105
+ @wd_locator = {xpath: ".//*[contains(local-name(), 'div')]"}
106
+ @data_locator = 'first div'
107
+ end
108
+
109
+ it 'with Symbol' do
110
+ @selector = {tag_name: :div}
111
+ @wd_locator = {xpath: ".//*[local-name()='div']"}
112
+ @data_locator = 'first div'
113
+ end
114
+
115
+ it 'raises exception when not a String or Regexp', skip_after: true do
116
+ selector = {tag_name: 7}
117
+ msg = /expected string_or_regexp_or_symbol, got 7:(Fixnum|Integer)/
118
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
119
+ end
120
+ end
121
+
122
+ context 'with class names' do
123
+ before(:each) do
124
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
125
+ end
126
+
127
+ it 'class_name is converted to class' do
128
+ @selector = {class_name: 'user'}
129
+ @wd_locator = {xpath: ".//*[contains(concat(' ', @class, ' '), ' user ')]"}
130
+ @data_locator = 'form'
131
+ end
132
+
133
+ # TODO: This functionality is deprecated with "class_array"
134
+ it 'values with spaces' do
135
+ @selector = {class_name: 'multiple classes here'}
136
+ @wd_locator = {xpath: ".//*[contains(concat(' ', @class, ' '), ' multiple classes here ')]"}
137
+ @data_locator = 'first div'
138
+ end
139
+
140
+ it 'single String concatenates' do
141
+ @selector = {class: 'user'}
142
+ @wd_locator = {xpath: ".//*[contains(concat(' ', @class, ' '), ' user ')]"}
143
+ @data_locator = 'form'
144
+ end
145
+
146
+ it 'Array of String concatenates with and' do
147
+ @selector = {class: %w[multiple here]}
148
+ @wd_locator = {xpath: ".//*[contains(concat(' ', @class, ' '), ' multiple ') and " \
149
+ "contains(concat(' ', @class, ' '), ' here ')]"}
150
+ @data_locator = 'first div'
151
+ end
152
+
153
+ it 'simple Regexp contains' do
154
+ @selector = {class_name: /use/}
155
+ @wd_locator = {xpath: ".//*[contains(@class, 'use')]"}
156
+ @data_locator = 'form'
157
+ end
158
+
159
+ it 'Array of Regexp contains with and' do
160
+ @selector = {class: [/mult/, /her/]}
161
+ @wd_locator = {xpath: ".//*[contains(@class, 'mult') and contains(@class, 'her')]"}
162
+ @data_locator = 'first div'
163
+ end
164
+
165
+ it 'single negated String concatenates with not' do
166
+ @selector = {class: '!multiple'}
167
+ @wd_locator = {xpath: ".//*[not(contains(concat(' ', @class, ' '), ' multiple '))]"}
168
+ @tag_name = 'html'
169
+ end
170
+
171
+ it 'single Boolean true provides the at' do
172
+ @selector = {class: true}
173
+ @wd_locator = {xpath: './/*[@class]'}
174
+ @data_locator = 'first div'
175
+ end
176
+
177
+ it 'single Boolean false provides the not atat' do
178
+ @selector = {class: false}
179
+ @wd_locator = {xpath: './/*[not(@class)]'}
180
+ @tag_name = 'html'
181
+ end
182
+
183
+ it 'Array of mixed String, Regexp and Boolean contains and concatenates with and and not' do
184
+ @selector = {class: [/mult/, 'classes', '!here']}
185
+ @wd_locator = {xpath: ".//*[contains(@class, 'mult') and contains(concat(' ', @class, ' '), ' classes ') " \
186
+ "and not(contains(concat(' ', @class, ' '), ' here '))]"}
187
+ @data_locator = 'second div'
188
+ end
189
+
190
+ it 'raises exception when not a String or Regexp or Array', skip_after: true do
191
+ selector = {class: 7}
192
+ msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:(Fixnum|Integer)/
193
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
194
+ end
195
+
196
+ it 'raises exception when Array values are not a String or Regexp', skip_after: true do
197
+ selector = {class: [7]}
198
+ msg = /expected string_or_regexp, got 7:(Fixnum|Integer)/
199
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
200
+ end
201
+
202
+ it 'raises exception when class and class_name are both used', skip_after: true do
203
+ selector = {class: 'foo', class_name: 'bar'}
204
+ msg = 'Can not use both :class and :class_name locators'
205
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
206
+ end
207
+
208
+ it 'raises exception when class array is empty', skip_after: true do
209
+ selector = {class: []}
210
+ msg = 'Can not locate elements with an empty Array for :class'
211
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
212
+ end
213
+ end
214
+
215
+ context 'with attributes as predicates' do
216
+ before(:each) do
217
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
218
+ end
219
+
220
+ it 'with href attribute' do
221
+ @selector = {href: 'watirspec.css'}
222
+ @wd_locator = {xpath: ".//*[normalize-space(@href)='watirspec.css']"}
223
+ @data_locator = 'link'
224
+ end
225
+
226
+ it 'with string attribute' do
227
+ @selector = {'name' => 'user_new'}
228
+ @wd_locator = {xpath: ".//*[@name='user_new']"}
229
+ @data_locator = 'form'
230
+ end
231
+
232
+ it 'with String equals' do
233
+ @selector = {name: 'user_new'}
234
+ @wd_locator = {xpath: ".//*[@name='user_new']"}
235
+ @data_locator = 'form'
236
+ end
237
+
238
+ it 'with TrueClass no equals' do
239
+ @selector = {tag_name: 'input', name: true}
240
+ @wd_locator = {xpath: ".//*[local-name()='input'][@name]"}
241
+ @data_locator = 'input name'
242
+ end
243
+
244
+ it 'with FalseClass not with no equals' do
245
+ @selector = {tag_name: 'input', name: false}
246
+ @wd_locator = {xpath: ".//*[local-name()='input'][not(@name)]"}
247
+ @data_locator = 'input nameless'
248
+ end
249
+
250
+ it 'with multiple attributes: no equals and not with no equals and equals' do
251
+ @selector = {readonly: true, foo: false, id: 'good_luck'}
252
+ @wd_locator = {xpath: ".//*[@readonly and not(@foo) and @id='good_luck']"}
253
+ @data_locator = 'Good Luck'
254
+ end
255
+
256
+ it 'raises exception when attribute value is not a Boolean, String or Regexp', skip_after: true do
257
+ selector = {foo: 7}
258
+ msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:(Fixnum|Integer)/
259
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
260
+ end
261
+
262
+ it 'raises exception when attribute key is not a String or Regexp', skip_after: true do
263
+ selector = {7 => 'foo'}
264
+ msg = /Unable to build XPath using 7:(Fixnum|Integer)/
265
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
266
+ end
267
+ end
268
+
269
+ context 'with attributes as partials' do
270
+ before(:each) do
271
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
272
+ end
273
+
274
+ it 'with Regexp' do
275
+ @selector = {name: /user/}
276
+ @wd_locator = {xpath: ".//*[contains(@name, 'user')]"}
277
+ @data_locator = 'form'
278
+ end
279
+
280
+ it 'with multiple Regexp attributes separated by and' do
281
+ @selector = {readonly: /read/, id: /good/}
282
+ @wd_locator = {xpath: ".//*[contains(@readonly, 'read') and contains(@id, 'good')]"}
283
+ @data_locator = 'Good Luck'
284
+ end
285
+ end
286
+
287
+ context 'with text' do
288
+ before(:each) do
289
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
290
+ end
291
+
292
+ it 'String uses normalize space equals' do
293
+ @selector = {text: 'Add user'}
294
+ @wd_locator = {xpath: ".//*[normalize-space()='Add user']"}
295
+ @data_locator = 'add user'
296
+ end
297
+
298
+ # Deprecated with :caption
299
+ it 'with caption attribute' do
300
+ @selector = {caption: 'Add user'}
301
+ @wd_locator = {xpath: ".//*[normalize-space()='Add user']"}
302
+ @data_locator = 'add user'
303
+ end
304
+
305
+ it 'raises exception when text is not a String or Regexp', skip_after: true do
306
+ selector = {text: 7}
307
+ msg = /expected string_or_regexp, got 7:(Fixnum|Integer)/
308
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
309
+ end
310
+ end
311
+
312
+ context 'with index' do
313
+ before(:each) do
314
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
315
+ end
316
+
317
+ it 'positive' do
318
+ @selector = {tag_name: 'div', index: 7}
319
+ @wd_locator = {xpath: "(.//*[local-name()='div'])[8]"}
320
+ @data_locator = 'content'
321
+ end
322
+
323
+ it 'negative' do
324
+ @selector = {tag_name: 'div', index: -7}
325
+ @wd_locator = {xpath: "(.//*[local-name()='div'])[last()-6]"}
326
+ @data_locator = 'second div'
327
+ end
328
+
329
+ it 'last' do
330
+ @selector = {tag_name: 'div', index: -1}
331
+ @wd_locator = {xpath: "(.//*[local-name()='div'])[last()]"}
332
+ @data_locator = 'content'
333
+ end
334
+
335
+ it 'does not return index if it is zero' do
336
+ @selector = {tag_name: 'div', index: 0}
337
+ @wd_locator = {xpath: ".//*[local-name()='div']"}
338
+ end
339
+
340
+ it 'raises exception when index is not an Integer', skip_after: true do
341
+ selector = {index: 'foo'}
342
+ msg = 'expected Integer, got "foo":String'
343
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
344
+ end
345
+ end
346
+
347
+ context 'with labels' do
348
+ before(:each) do
349
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
350
+ end
351
+
352
+ it 'locates the element associated with the label element located by the text of the provided label key' do
353
+ @selector = {label: 'Cars'}
354
+ @wd_locator = {xpath: ".//*[(@id=//label[normalize-space()='Cars']/@for "\
355
+ "or parent::label[normalize-space()='Cars'])]"}
356
+ @data_locator = 'cars'
357
+ end
358
+
359
+ it 'does not use the label element when label is a valid attribute' do
360
+ @attributes ||= Watir::Option.attribute_list
361
+
362
+ @selector = {tag_name: 'option', label: 'Germany'}
363
+ @wd_locator = {xpath: ".//*[local-name()='option'][@label='Germany']"}
364
+ @data_locator = 'Berliner'
365
+ end
366
+ end
367
+
368
+ context 'with adjacent locators' do
369
+ before(:each) do
370
+ browser.goto(WatirSpec.url_for('nested_elements.html'))
371
+ end
372
+
373
+ it 'raises exception when not a Symbol', skip_after: true do
374
+ selector = {adjacent: 'foo', index: 0}
375
+ msg = 'expected Symbol, got "foo":String'
376
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
377
+ end
378
+
379
+ it 'raises exception when not a valid value', skip_after: true do
380
+ selector = {adjacent: :foo, index: 0}
381
+ msg = 'Unable to process adjacent locator with foo'
382
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
383
+ end
384
+
385
+ describe '#parent' do
386
+ it 'with no other arguments' do
387
+ @query_scope = browser.div(id: 'first_sibling')
388
+ @selector = {adjacent: :ancestor, index: 0}
389
+ @wd_locator = {xpath: './ancestor::*[1]'}
390
+ @data_locator = 'parent'
391
+ end
392
+
393
+ it 'with index' do
394
+ @query_scope = browser.div(id: 'first_sibling')
395
+ @selector = {adjacent: :ancestor, index: 2}
396
+ @wd_locator = {xpath: './ancestor::*[3]'}
397
+ @data_locator = 'grandparent'
398
+ end
399
+
400
+ it 'with multiple locators' do
401
+ @query_scope = browser.div(id: 'first_sibling')
402
+ @selector = {adjacent: :ancestor, id: true, tag_name: 'div', class: 'ancestor', index: 1}
403
+ @wd_locator = {xpath: "./ancestor::*[local-name()='div']"\
404
+ "[contains(concat(' ', @class, ' '), ' ancestor ')][@id][2]"}
405
+ @data_locator = 'grandparent'
406
+ end
407
+
408
+ it 'raises an exception if text locator is used', skip_after: true do
409
+ selector = {adjacent: :ancestor, index: 0, text: 'Foo'}
410
+ msg = 'Can not find parent element with text locator'
411
+ expect { selector_builder.build(selector) }
412
+ .to raise_exception Watir::Exception::LocatorException, msg
413
+ end
414
+ end
415
+
416
+ describe '#following_sibling' do
417
+ it 'with no other arguments' do
418
+ @query_scope = browser.div(id: 'first_sibling')
419
+ @selector = {adjacent: :following, index: 0}
420
+ @wd_locator = {xpath: './following-sibling::*[1]'}
421
+ @data_locator = 'between_siblings1'
422
+ end
423
+
424
+ it 'with index' do
425
+ @query_scope = browser.div(id: 'first_sibling')
426
+ @selector = {adjacent: :following, index: 2}
427
+ @wd_locator = {xpath: './following-sibling::*[3]'}
428
+ @data_locator = 'between_siblings2'
429
+ end
430
+
431
+ it 'with multiple locators' do
432
+ @query_scope = browser.div(id: 'first_sibling')
433
+ @selector = {adjacent: :following, tag_name: 'div', class: 'b', index: 0, id: true}
434
+ @wd_locator = {xpath: "./following-sibling::*[local-name()='div']"\
435
+ "[contains(concat(' ', @class, ' '), ' b ')][@id][1]"}
436
+ @data_locator = 'second_sibling'
437
+ end
438
+
439
+ it 'with text' do
440
+ @query_scope = browser.div(id: 'first_sibling')
441
+ @selector = {adjacent: :following, text: 'Third', index: 0}
442
+ @wd_locator = {xpath: "./following-sibling::*[normalize-space()='Third'][1]"}
443
+ @data_locator = 'third_sibling'
444
+ end
445
+ end
446
+
447
+ describe '#previous_sibling' do
448
+ it 'with no other arguments' do
449
+ @query_scope = browser.div(id: 'third_sibling')
450
+ @selector = {adjacent: :preceding, index: 0}
451
+ @wd_locator = {xpath: './preceding-sibling::*[1]'}
452
+ @data_locator = 'between_siblings2'
453
+ end
454
+
455
+ it 'with index' do
456
+ @query_scope = browser.div(id: 'third_sibling')
457
+ @selector = {adjacent: :preceding, index: 2}
458
+ @wd_locator = {xpath: './preceding-sibling::*[3]'}
459
+ @data_locator = 'between_siblings1'
460
+ end
461
+
462
+ it 'with multiple locators' do
463
+ @query_scope = browser.div(id: 'third_sibling')
464
+ @selector = {adjacent: :preceding, tag_name: 'div', class: 'b', id: true, index: 0}
465
+ @wd_locator = {xpath: "./preceding-sibling::*[local-name()='div']"\
466
+ "[contains(concat(' ', @class, ' '), ' b ')][@id][1]"}
467
+ @data_locator = 'second_sibling'
468
+ end
469
+
470
+ it 'with text' do
471
+ @query_scope = browser.div(id: 'third_sibling')
472
+ @selector = {adjacent: :preceding, text: 'Second', index: 0}
473
+ @wd_locator = {xpath: "./preceding-sibling::*[normalize-space()='Second'][1]"}
474
+ @data_locator = 'second_sibling'
475
+ end
476
+ end
477
+
478
+ describe '#child' do
479
+ it 'with no other arguments' do
480
+ @query_scope = browser.div(id: 'first_sibling')
481
+ @selector = {adjacent: :child, index: 0}
482
+ @wd_locator = {xpath: './child::*[1]'}
483
+ @data_locator = 'child span'
484
+ end
485
+
486
+ it 'with index' do
487
+ @query_scope = browser.div(id: 'parent')
488
+ @selector = {adjacent: :child, index: 2}
489
+ @wd_locator = {xpath: './child::*[3]'}
490
+ @data_locator = 'second_sibling'
491
+ end
492
+
493
+ it 'with multiple locators' do
494
+ @query_scope = browser.div(id: 'parent')
495
+ @selector = {adjacent: :child, tag_name: 'div', class: 'b', id: true, index: 0}
496
+ @wd_locator = {xpath: "./child::*[local-name()='div']"\
497
+ "[contains(concat(' ', @class, ' '), ' b ')][@id][1]"}
498
+ @data_locator = 'second_sibling'
499
+ end
500
+
501
+ it 'with text' do
502
+ @query_scope = browser.div(id: 'parent')
503
+ @selector = {adjacent: :child, text: 'Second', index: 0}
504
+ @wd_locator = {xpath: "./child::*[normalize-space()='Second'][1]"}
505
+ @data_locator = 'second_sibling'
506
+ end
507
+ end
508
+ end
509
+
510
+ context 'with multiple locators' do
511
+ before(:each) do
512
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
513
+ end
514
+
515
+ it 'locates using tag name, class, attributes and text' do
516
+ @selector = {tag_name: 'div', class: 'content', contenteditable: 'true', text: 'Foo'}
517
+ @wd_locator = {xpath: ".//*[local-name()='div'][contains(concat(' ', @class, ' '), ' content ')]" \
518
+ "[normalize-space()='Foo'][@contenteditable='true']"}
519
+ @data_locator = 'content'
520
+ end
521
+ end
522
+
523
+ context 'with simple Regexp' do
524
+ before(:each) do
525
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
526
+ end
527
+
528
+ it 'handles spaces' do
529
+ @selector = {title: /od Lu/}
530
+ @wd_locator = {xpath: ".//*[contains(@title, 'od Lu')]"}
531
+ @data_locator = 'Good Luck'
532
+ end
533
+
534
+ it 'handles escaped characters' do
535
+ @selector = {src: /ages\/but/}
536
+ @wd_locator = {xpath: ".//*[contains(@src, 'ages/but')]"}
537
+ @data_locator = 'submittable button'
538
+ end
539
+ end
540
+
541
+ context 'with complex Regexp' do
542
+ before(:each) do
543
+ browser.goto(WatirSpec.url_for('forms_with_input_elements.html'))
544
+ end
545
+
546
+ it 'handles wildcards' do
547
+ @selector = {src: /ages.*but/}
548
+ @wd_locator = {xpath: ".//*[contains(@src, 'ages') and contains(@src, 'but')]"}
549
+ @data_locator = 'submittable button'
550
+ @remaining = {src: /ages.*but/}
551
+ end
552
+
553
+ it 'handles optional characters' do
554
+ @selector = {src: /ages ?but/}
555
+ @wd_locator = {xpath: ".//*[contains(@src, 'ages') and contains(@src, 'but')]"}
556
+ @data_locator = 'submittable button'
557
+ @remaining = {src: /ages ?but/}
558
+ end
559
+
560
+ it 'handles anchors' do
561
+ @selector = {name: /^new_user_image$/}
562
+ @wd_locator = {xpath: ".//*[contains(@name, 'new_user_image')]"}
563
+ @data_locator = 'submittable button'
564
+ @remaining = {name: /^new_user_image$/}
565
+ end
566
+
567
+ it 'handles beginning anchor' do
568
+ @selector = {src: /^i/}
569
+ @wd_locator = {xpath: ".//*[starts-with(@src, 'i')]"}
570
+ @data_locator = 'submittable button'
571
+ end
572
+
573
+ it 'does not use starts-with if visible locator used' do
574
+ @selector = {id: /^vis/, visible_text: 'shown div'}
575
+ @wd_locator = {xpath: ".//*[contains(@id, 'vis')]"}
576
+ @remaining = {id: /^vis/, visible_text: 'shown div'}
577
+ end
578
+
579
+ it 'handles case insensitive' do
580
+ @selector = {action: /me/i}
581
+ @wd_locator = {xpath: './/*[contains(translate(@action,' \
582
+ "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
583
+ "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'), 'me')]"}
584
+ @data_locator = 'form'
585
+ end
586
+ end
587
+
588
+ # TODO: These can be moved to unit tests since no browser required
589
+ context 'returns locators that can not be directly translated' do
590
+ it 'attribute with complicated Regexp at end' do
591
+ @selector = {action: /me$/}
592
+ @wd_locator = {xpath: ".//*[contains(@action, 'me')]"}
593
+ @remaining = {action: /me$/}
594
+ end
595
+
596
+ it 'class with complicated Regexp' do
597
+ @selector = {class: /he?r/}
598
+ @wd_locator = {xpath: ".//*[contains(@class, 'h') and contains(@class, 'r')]"}
599
+ @remaining = {class: [/he?r/]}
600
+ end
601
+
602
+ it 'text with any Regexp' do
603
+ @selector = {text: /Add/}
604
+ @wd_locator = {xpath: './/*'}
605
+ @remaining = {text: /Add/}
606
+ end
607
+
608
+ it 'visible' do
609
+ @selector = {tag_name: 'div', visible: true}
610
+ @wd_locator = {xpath: ".//*[local-name()='div']"}
611
+ @remaining = {visible: true}
612
+ end
613
+
614
+ it 'not visible' do
615
+ @selector = {tag_name: 'span', visible: false}
616
+ @wd_locator = {xpath: ".//*[local-name()='span']"}
617
+ @remaining = {visible: false}
618
+ end
619
+
620
+ it 'visible text' do
621
+ @selector = {tag_name: 'span', visible_text: 'foo'}
622
+ @wd_locator = {xpath: ".//*[local-name()='span']"}
623
+ @remaining = {visible_text: 'foo'}
624
+ end
625
+
626
+ it 'raises exception when visible is not boolean', skip_after: true do
627
+ selector = {visible: 'foo'}
628
+ msg = 'expected boolean, got "foo":String'
629
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
630
+ end
631
+
632
+ it 'raises exception when visible text is not a String or Regexp', skip_after: true do
633
+ selector = {visible_text: 7}
634
+ msg = /expected string_or_regexp, got 7:(Fixnum|Integer)/
635
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
636
+ end
637
+ end
638
+ end
639
+ end