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
@@ -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