watir 7.2.0 → 7.2.1

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 (139) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/chrome.yml +1 -1
  3. data/.github/workflows/edge.yml +1 -1
  4. data/.github/workflows/firefox.yml +1 -1
  5. data/.github/workflows/ie.yml +1 -1
  6. data/.github/workflows/safari.yml +1 -1
  7. data/.github/workflows/unit.yml +1 -1
  8. data/.rubocop.yml +25 -12
  9. data/.rubocop_todo.yml +8 -1
  10. data/CHANGES.md +6 -1
  11. data/LICENSE +2 -2
  12. data/lib/watir/capabilities.rb +1 -1
  13. data/lib/watir/locators/anchor/selector_builder.rb +3 -0
  14. data/lib/watir/locators/element/matcher.rb +2 -1
  15. data/lib/watir/locators/element/selector_builder/regexp_disassembler.rb +2 -1
  16. data/lib/watir/locators/text_field/matcher.rb +0 -4
  17. data/lib/watir/version.rb +1 -1
  18. data/spec/locator_spec_helper.rb +0 -8
  19. data/spec/unit/capabilities_spec.rb +553 -561
  20. data/spec/unit/element_locator_spec.rb +89 -78
  21. data/spec/unit/match_elements/button_spec.rb +69 -62
  22. data/spec/unit/match_elements/element_spec.rb +303 -309
  23. data/spec/unit/match_elements/text_field_spec.rb +76 -73
  24. data/spec/unit/selector_builder/anchor_spec.rb +39 -33
  25. data/spec/unit/selector_builder/button_spec.rb +209 -202
  26. data/spec/unit/selector_builder/cell_spec.rb +48 -42
  27. data/spec/unit/selector_builder/element_spec.rb +634 -627
  28. data/spec/unit/selector_builder/row_spec.rb +119 -113
  29. data/spec/unit/selector_builder/text_field_spec.rb +195 -188
  30. data/spec/unit/selector_builder/textarea_spec.rb +22 -14
  31. data/spec/unit/wait_spec.rb +85 -81
  32. data/spec/watirspec/adjacent_spec.rb +249 -247
  33. data/spec/watirspec/after_hooks_spec.rb +161 -159
  34. data/spec/watirspec/alert_spec.rb +61 -58
  35. data/spec/watirspec/browser_spec.rb +409 -409
  36. data/spec/watirspec/capabilities_spec.rb +89 -99
  37. data/spec/watirspec/cookies_spec.rb +120 -118
  38. data/spec/watirspec/drag_and_drop_spec.rb +26 -24
  39. data/spec/watirspec/element_hidden_spec.rb +66 -60
  40. data/spec/watirspec/elements/area_spec.rb +49 -47
  41. data/spec/watirspec/elements/areas_spec.rb +27 -25
  42. data/spec/watirspec/elements/button_spec.rb +253 -251
  43. data/spec/watirspec/elements/buttons_spec.rb +36 -34
  44. data/spec/watirspec/elements/checkbox_spec.rb +48 -36
  45. data/spec/watirspec/elements/checkboxes_spec.rb +29 -27
  46. data/spec/watirspec/elements/collections_spec.rb +126 -119
  47. data/spec/watirspec/elements/date_field_spec.rb +185 -183
  48. data/spec/watirspec/elements/date_fields_spec.rb +29 -27
  49. data/spec/watirspec/elements/date_time_field_spec.rb +197 -195
  50. data/spec/watirspec/elements/date_time_fields_spec.rb +30 -28
  51. data/spec/watirspec/elements/dd_spec.rb +87 -85
  52. data/spec/watirspec/elements/dds_spec.rb +27 -25
  53. data/spec/watirspec/elements/del_spec.rb +106 -104
  54. data/spec/watirspec/elements/dels_spec.rb +26 -24
  55. data/spec/watirspec/elements/div_spec.rb +225 -221
  56. data/spec/watirspec/elements/divs_spec.rb +37 -35
  57. data/spec/watirspec/elements/dl_spec.rb +110 -108
  58. data/spec/watirspec/elements/dls_spec.rb +28 -26
  59. data/spec/watirspec/elements/dt_spec.rb +86 -84
  60. data/spec/watirspec/elements/dts_spec.rb +27 -25
  61. data/spec/watirspec/elements/element_spec.rb +794 -775
  62. data/spec/watirspec/elements/elements_spec.rb +40 -38
  63. data/spec/watirspec/elements/em_spec.rb +62 -60
  64. data/spec/watirspec/elements/ems_spec.rb +28 -26
  65. data/spec/watirspec/elements/filefield_spec.rb +116 -114
  66. data/spec/watirspec/elements/filefields_spec.rb +28 -26
  67. data/spec/watirspec/elements/font_spec.rb +20 -18
  68. data/spec/watirspec/elements/form_spec.rb +51 -49
  69. data/spec/watirspec/elements/forms_spec.rb +29 -27
  70. data/spec/watirspec/elements/frame_spec.rb +89 -87
  71. data/spec/watirspec/elements/frames_spec.rb +27 -25
  72. data/spec/watirspec/elements/hidden_spec.rb +81 -79
  73. data/spec/watirspec/elements/hiddens_spec.rb +28 -26
  74. data/spec/watirspec/elements/hn_spec.rb +59 -57
  75. data/spec/watirspec/elements/hns_spec.rb +26 -24
  76. data/spec/watirspec/elements/iframe_spec.rb +195 -191
  77. data/spec/watirspec/elements/iframes_spec.rb +31 -29
  78. data/spec/watirspec/elements/image_spec.rb +148 -146
  79. data/spec/watirspec/elements/images_spec.rb +26 -24
  80. data/spec/watirspec/elements/input_spec.rb +9 -7
  81. data/spec/watirspec/elements/ins_spec.rb +106 -104
  82. data/spec/watirspec/elements/inses_spec.rb +26 -24
  83. data/spec/watirspec/elements/label_spec.rb +56 -54
  84. data/spec/watirspec/elements/labels_spec.rb +26 -24
  85. data/spec/watirspec/elements/li_spec.rb +76 -74
  86. data/spec/watirspec/elements/link_spec.rb +173 -177
  87. data/spec/watirspec/elements/links_spec.rb +45 -43
  88. data/spec/watirspec/elements/lis_spec.rb +27 -25
  89. data/spec/watirspec/elements/list_spec.rb +44 -42
  90. data/spec/watirspec/elements/map_spec.rb +57 -55
  91. data/spec/watirspec/elements/maps_spec.rb +27 -25
  92. data/spec/watirspec/elements/meta_spec.rb +16 -14
  93. data/spec/watirspec/elements/metas_spec.rb +26 -24
  94. data/spec/watirspec/elements/ol_spec.rb +56 -65
  95. data/spec/watirspec/elements/ols_spec.rb +26 -24
  96. data/spec/watirspec/elements/option_spec.rb +91 -89
  97. data/spec/watirspec/elements/p_spec.rb +81 -79
  98. data/spec/watirspec/elements/pre_spec.rb +77 -75
  99. data/spec/watirspec/elements/pres_spec.rb +26 -24
  100. data/spec/watirspec/elements/ps_spec.rb +26 -24
  101. data/spec/watirspec/elements/radio_spec.rb +251 -249
  102. data/spec/watirspec/elements/radios_spec.rb +28 -26
  103. data/spec/watirspec/elements/select_list_spec.rb +537 -526
  104. data/spec/watirspec/elements/select_lists_spec.rb +32 -30
  105. data/spec/watirspec/elements/span_spec.rb +112 -110
  106. data/spec/watirspec/elements/spans_spec.rb +26 -24
  107. data/spec/watirspec/elements/strong_spec.rb +58 -58
  108. data/spec/watirspec/elements/strongs_spec.rb +28 -26
  109. data/spec/watirspec/elements/table_spec.rb +192 -139
  110. data/spec/watirspec/elements/tables_spec.rb +28 -26
  111. data/spec/watirspec/elements/tbody_spec.rb +74 -72
  112. data/spec/watirspec/elements/tbodys_spec.rb +40 -38
  113. data/spec/watirspec/elements/td_spec.rb +66 -64
  114. data/spec/watirspec/elements/tds_spec.rb +41 -39
  115. data/spec/watirspec/elements/text_field_spec.rb +206 -204
  116. data/spec/watirspec/elements/text_fields_spec.rb +30 -28
  117. data/spec/watirspec/elements/textarea_spec.rb +20 -18
  118. data/spec/watirspec/elements/textareas_spec.rb +18 -16
  119. data/spec/watirspec/elements/tfoot_spec.rb +65 -63
  120. data/spec/watirspec/elements/tfoots_spec.rb +42 -40
  121. data/spec/watirspec/elements/thead_spec.rb +65 -63
  122. data/spec/watirspec/elements/theads_spec.rb +42 -40
  123. data/spec/watirspec/elements/tr_spec.rb +48 -46
  124. data/spec/watirspec/elements/trs_spec.rb +42 -40
  125. data/spec/watirspec/elements/ul_spec.rb +56 -54
  126. data/spec/watirspec/elements/uls_spec.rb +26 -24
  127. data/spec/watirspec/radio_set_spec.rb +269 -267
  128. data/spec/watirspec/screenshot_spec.rb +20 -18
  129. data/spec/watirspec/scroll_spec.rb +152 -148
  130. data/spec/watirspec/shadow_root_spec.rb +86 -83
  131. data/spec/watirspec/user_editable_spec.rb +200 -198
  132. data/spec/watirspec/wait_spec.rb +289 -295
  133. data/spec/watirspec/window_switching_spec.rb +422 -434
  134. data/spec/watirspec_helper.rb +1 -23
  135. data/watir.gemspec +5 -4
  136. metadata +44 -33
  137. data/spec/watirspec/attributes_spec.rb +0 -18
  138. data/spec/watirspec/elements/table_nesting_spec.rb +0 -51
  139. data/spec/watirspec/special_chars_spec.rb +0 -23
@@ -2,797 +2,804 @@
2
2
 
3
3
  require_relative '../unit_helper'
4
4
 
5
- describe Watir::Locators::Element::SelectorBuilder do
6
- include LocatorSpecHelper
5
+ module Watir
6
+ module Locators
7
+ class Element
8
+ describe SelectorBuilder do
9
+ include LocatorSpecHelper
7
10
 
8
- let(:selector_builder) { described_class.new(attributes, query_scope) }
11
+ let(:selector_builder) { described_class.new(attributes, query_scope) }
9
12
 
10
- describe '#build' do
11
- it 'without any arguments' do
12
- selector = {}
13
- built = {xpath: './/*'}
13
+ describe '#build' do
14
+ it 'without any arguments' do
15
+ selector = {}
16
+ built = {xpath: './/*'}
14
17
 
15
- expect(selector_builder.build(selector)).to eq built
16
- end
17
-
18
- context 'with xpath or css' do
19
- it 'locates with xpath only' do
20
- selector = {xpath: './/div'}
21
- built = selector.dup
22
-
23
- expect(selector_builder.build(selector)).to eq built
24
- end
25
-
26
- it 'locates with css only' do
27
- selector = {css: 'div'}
28
- built = selector.dup
29
-
30
- expect(selector_builder.build(selector)).to eq built
31
- end
32
-
33
- it 'locates when attributes combined with xpath' do
34
- selector = {xpath: './/div', random: 'foo'}
35
- built = selector.dup
36
-
37
- expect(selector_builder.build(selector)).to eq built
38
- end
39
-
40
- it 'locates when attributes combined with css' do
41
- selector = {css: 'div', random: 'foo'}
42
- built = selector.dup
43
-
44
- expect(selector_builder.build(selector)).to eq built
45
- end
46
-
47
- it 'raises exception when using xpath & css' do
48
- selector = {xpath: './/*', css: 'div'}
49
- msg = 'Can not locate element with [:css, :xpath]'
50
-
51
- expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
52
- end
53
-
54
- it 'raises exception when not a String' do
55
- selector = {xpath: 7}
56
- msg = /expected one of \[String\], got 7:Integer/
57
-
58
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
59
- end
60
- end
61
-
62
- context 'with tag_name' do
63
- it 'with String equals' do
64
- selector = {tag_name: 'div'}
65
- built = {xpath: ".//*[local-name()='div']"}
66
-
67
- expect(selector_builder.build(selector)).to eq built
68
- end
69
-
70
- it 'with simple Regexp contains' do
71
- selector = {tag_name: /div/}
72
- built = {xpath: './/*[contains(local-name(), "div")]'}
73
-
74
- expect(selector_builder.build(selector)).to eq built
75
- end
76
-
77
- it 'with Symbol' do
78
- selector = {tag_name: :div}
79
- built = {xpath: ".//*[local-name()='div']"}
80
-
81
- expect(selector_builder.build(selector)).to eq built
82
- end
83
-
84
- it 'raises exception when not a String or Regexp' do
85
- selector = {tag_name: 7}
86
- msg = /expected one of \[String, Regexp, Symbol\], got 7:Integer/
87
-
88
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
89
- end
90
- end
91
-
92
- context 'with class names' do
93
- it 'class_name is converted to class' do
94
- selector = {class_name: 'user'}
95
- built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' user ')]"}
96
-
97
- expect(selector_builder.build(selector)).to eq built
98
- end
99
-
100
- it 'single String concatenates' do
101
- selector = {class: 'user'}
102
- built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' user ')]"}
103
-
104
- expect(selector_builder.build(selector)).to eq built
105
- end
106
-
107
- it 'Array of String concatenates with and' do
108
- selector = {class: %w[multiple here]}
109
- built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' multiple ') and " \
110
- "contains(concat(' ', normalize-space(@class), ' '), ' here ')]"}
111
-
112
- expect(selector_builder.build(selector)).to eq built
113
- end
114
-
115
- it 'merges values when class and class_name are both used' do
116
- selector = {class: 'foo', class_name: 'bar'}
117
- built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' foo ') and " \
118
- "contains(concat(' ', normalize-space(@class), ' '), ' bar ')]"}
18
+ expect(selector_builder.build(selector)).to eq built
19
+ end
119
20
 
120
- expect(selector_builder.build(selector)).to eq built
121
- end
21
+ context 'with xpath or css' do
22
+ it 'locates with xpath only' do
23
+ selector = {xpath: './/div'}
24
+ built = selector.dup
122
25
 
123
- it 'simple Regexp contains' do
124
- selector = {class_name: /use/}
125
- built = {xpath: './/*[contains(@class, "use")]'}
26
+ expect(selector_builder.build(selector)).to eq built
27
+ end
126
28
 
127
- expect(selector_builder.build(selector)).to eq built
128
- end
29
+ it 'locates with css only' do
30
+ selector = {css: 'div'}
31
+ built = selector.dup
129
32
 
130
- it 'Array of Regexp contains with and' do
131
- selector = {class: [/mult/, /her/]}
132
- built = {xpath: './/*[contains(@class, "mult") and contains(@class, "her")]'}
33
+ expect(selector_builder.build(selector)).to eq built
34
+ end
133
35
 
134
- expect(selector_builder.build(selector)).to eq built
135
- end
36
+ it 'locates when attributes combined with xpath' do
37
+ selector = {xpath: './/div', random: 'foo'}
38
+ built = selector.dup
136
39
 
137
- it 'single negated String concatenates with not' do
138
- selector = {class: '!multiple'}
139
- built = {xpath: ".//*[not(contains(concat(' ', normalize-space(@class), ' '), ' multiple '))]"}
140
-
141
- expect(selector_builder.build(selector)).to eq built
142
- end
143
-
144
- it 'single Boolean true provides the at' do
145
- selector = {class: true}
146
- built = {xpath: './/*[@class]'}
147
-
148
- expect(selector_builder.build(selector)).to eq built
149
- end
150
-
151
- it 'single Boolean false provides the not atat' do
152
- selector = {class: false}
153
- built = {xpath: './/*[not(@class)]'}
154
-
155
- expect(selector_builder.build(selector)).to eq built
156
- end
157
-
158
- it 'Array of mixed String, Regexp and Boolean contains and concatenates with and and not' do
159
- selector = {class: [/mult/, 'classes', '!here']}
160
- built = {xpath: './/*[contains(@class, "mult") ' \
161
- "and contains(concat(' ', normalize-space(@class), ' '), ' classes ') " \
162
- "and not(contains(concat(' ', normalize-space(@class), ' '), ' here '))]"}
163
-
164
- expect(selector_builder.build(selector)).to eq built
165
- end
40
+ expect(selector_builder.build(selector)).to eq built
41
+ end
166
42
 
167
- it 'empty string finds elements without class' do
168
- selector = {class_name: ''}
169
- built = {xpath: './/*[not(@class)]'}
43
+ it 'locates when attributes combined with css' do
44
+ selector = {css: 'div', random: 'foo'}
45
+ built = selector.dup
170
46
 
171
- expect(selector_builder.build(selector)).to eq built
172
- end
47
+ expect(selector_builder.build(selector)).to eq built
48
+ end
173
49
 
174
- it 'empty Array finds elements without class' do
175
- selector = {class_name: []}
176
- built = {xpath: './/*[not(@class)]'}
50
+ it 'raises exception when using xpath & css' do
51
+ selector = {xpath: './/*', css: 'div'}
52
+ msg = 'Can not locate element with [:css, :xpath]'
177
53
 
178
- expect(selector_builder.build(selector)).to eq built
179
- end
54
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
55
+ end
180
56
 
181
- it 'raises exception when not a String or Regexp or Array' do
182
- selector = {class: 7}
183
- msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:Integer/
57
+ it 'raises exception when not a String' do
58
+ selector = {xpath: 7}
59
+ msg = /expected one of \[String\], got 7:Integer/
184
60
 
185
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
186
- end
61
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
62
+ end
63
+ end
187
64
 
188
- it 'raises exception when Array values are not a String or Regexp' do
189
- selector = {class: [7]}
190
- msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:Integer/
65
+ context 'with tag_name' do
66
+ it 'with String equals' do
67
+ selector = {tag_name: 'div'}
68
+ built = {xpath: ".//*[local-name()='div']"}
191
69
 
192
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
193
- end
194
- end
70
+ expect(selector_builder.build(selector)).to eq built
71
+ end
195
72
 
196
- context 'with attributes as predicates' do
197
- it 'with href attribute' do
198
- selector = {href: 'watirspec.css'}
199
- built = {xpath: ".//*[normalize-space(@href)='watirspec.css']"}
73
+ it 'with simple Regexp contains' do
74
+ selector = {tag_name: /div/}
75
+ built = {xpath: './/*[contains(local-name(), "div")]'}
200
76
 
201
- expect(selector_builder.build(selector)).to eq built
202
- end
77
+ expect(selector_builder.build(selector)).to eq built
78
+ end
203
79
 
204
- it 'with String attribute key' do
205
- selector = {'id' => 'user_new'}
206
- built = {xpath: ".//*[@id='user_new']"}
80
+ it 'with Symbol' do
81
+ selector = {tag_name: :div}
82
+ built = {xpath: ".//*[local-name()='div']"}
207
83
 
208
- expect(selector_builder.build(selector)).to eq built
209
- end
84
+ expect(selector_builder.build(selector)).to eq built
85
+ end
210
86
 
211
- it 'with String equals' do
212
- selector = {id: 'user_new'}
213
- built = {xpath: ".//*[@id='user_new']"}
87
+ it 'raises exception when not a String or Regexp' do
88
+ selector = {tag_name: 7}
89
+ msg = /expected one of \[String, Regexp, Symbol\], got 7:Integer/
214
90
 
215
- expect(selector_builder.build(selector)).to eq built
216
- end
91
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
92
+ end
93
+ end
217
94
 
218
- it 'with TrueClass no equals' do
219
- selector = {tag_name: 'input', id: true}
220
- built = {xpath: ".//*[local-name()='input'][@id]"}
95
+ context 'with class names' do
96
+ it 'class_name is converted to class' do
97
+ selector = {class_name: 'user'}
98
+ built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' user ')]"}
221
99
 
222
- expect(selector_builder.build(selector)).to eq built
223
- end
100
+ expect(selector_builder.build(selector)).to eq built
101
+ end
224
102
 
225
- it 'with FalseClass not with no equals' do
226
- selector = {tag_name: 'input', name: false}
227
- built = {xpath: ".//*[local-name()='input'][not(@name)]"}
103
+ it 'single String concatenates' do
104
+ selector = {class: 'user'}
105
+ built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' user ')]"}
228
106
 
229
- expect(selector_builder.build(selector)).to eq built
230
- end
107
+ expect(selector_builder.build(selector)).to eq built
108
+ end
231
109
 
232
- it 'with multiple attributes: no equals and not with no equals and equals' do
233
- selector = {readonly: true, foo: false, id: 'good_luck'}
234
- built = {xpath: ".//*[@readonly and not(@foo) and @id='good_luck']"}
110
+ it 'Array of String concatenates with and' do
111
+ selector = {class: %w[multiple here]}
112
+ built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' multiple ') and " \
113
+ "contains(concat(' ', normalize-space(@class), ' '), ' here ')]"}
235
114
 
236
- expect(selector_builder.build(selector)).to eq built
237
- end
115
+ expect(selector_builder.build(selector)).to eq built
116
+ end
238
117
 
239
- it 'raises exception when attribute value is not a Boolean, String or Regexp' do
240
- selector = {foo: 7}
241
- msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:Integer/
118
+ it 'merges values when class and class_name are both used' do
119
+ selector = {class: 'foo', class_name: 'bar'}
120
+ built = {xpath: ".//*[contains(concat(' ', normalize-space(@class), ' '), ' foo ') and " \
121
+ "contains(concat(' ', normalize-space(@class), ' '), ' bar ')]"}
242
122
 
243
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
244
- end
123
+ expect(selector_builder.build(selector)).to eq built
124
+ end
245
125
 
246
- it 'raises exception when attribute key is not a String or Regexp' do
247
- selector = {7 => 'foo'}
248
- msg = /Unable to build XPath using 7:Integer/
126
+ it 'simple Regexp contains' do
127
+ selector = {class_name: /use/}
128
+ built = {xpath: './/*[contains(@class, "use")]'}
249
129
 
250
- expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
251
- end
252
- end
130
+ expect(selector_builder.build(selector)).to eq built
131
+ end
253
132
 
254
- context 'with attributes as partials' do
255
- it 'with Regexp' do
256
- selector = {name: /user/}
257
- built = {xpath: './/*[contains(@name, "user")]'}
133
+ it 'Array of Regexp contains with and' do
134
+ selector = {class: [/mult/, /her/]}
135
+ built = {xpath: './/*[contains(@class, "mult") and contains(@class, "her")]'}
258
136
 
259
- expect(selector_builder.build(selector)).to eq built
260
- end
137
+ expect(selector_builder.build(selector)).to eq built
138
+ end
261
139
 
262
- it 'with multiple Regexp attributes separated by and' do
263
- selector = {readonly: /read/, id: /good/}
264
- built = {xpath: './/*[contains(@readonly, "read") and contains(@id, "good")]'}
140
+ it 'single negated String concatenates with not' do
141
+ selector = {class: '!multiple'}
142
+ built = {xpath: ".//*[not(contains(concat(' ', normalize-space(@class), ' '), ' multiple '))]"}
265
143
 
266
- expect(selector_builder.build(selector)).to eq built
267
- end
268
- end
144
+ expect(selector_builder.build(selector)).to eq built
145
+ end
269
146
 
270
- context 'with text' do
271
- it 'String uses normalize space equals' do
272
- selector = {text: 'Add user'}
273
- built = {xpath: ".//*[normalize-space()='Add user']"}
147
+ it 'single Boolean true provides the at' do
148
+ selector = {class: true}
149
+ built = {xpath: './/*[@class]'}
274
150
 
275
- expect(selector_builder.build(selector)).to eq built
276
- end
151
+ expect(selector_builder.build(selector)).to eq built
152
+ end
277
153
 
278
- it 'Regexp uses contains normalize space' do
279
- selector = {text: /Add/}
280
- built = {xpath: './/*[contains(normalize-space(), "Add")]'}
154
+ it 'single Boolean false provides the not atat' do
155
+ selector = {class: false}
156
+ built = {xpath: './/*[not(@class)]'}
281
157
 
282
- expect(selector_builder.build(selector)).to eq built
283
- end
158
+ expect(selector_builder.build(selector)).to eq built
159
+ end
284
160
 
285
- it 'raises exception when text is not a String or Regexp' do
286
- selector = {text: 7}
287
- msg = /expected one of \[String, Regexp\], got 7:Integer/
161
+ it 'Array of mixed String, Regexp and Boolean contains and concatenates with and and not' do
162
+ selector = {class: [/mult/, 'classes', '!here']}
163
+ built = {xpath: './/*[contains(@class, "mult") ' \
164
+ "and contains(concat(' ', normalize-space(@class), ' '), ' classes ') " \
165
+ "and not(contains(concat(' ', normalize-space(@class), ' '), ' here '))]"}
288
166
 
289
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
290
- end
291
- end
167
+ expect(selector_builder.build(selector)).to eq built
168
+ end
292
169
 
293
- context 'with index' do
294
- it 'positive' do
295
- selector = {tag_name: 'div', index: 7}
296
- built = {xpath: "(.//*[local-name()='div'])[8]"}
170
+ it 'empty string finds elements without class' do
171
+ selector = {class_name: ''}
172
+ built = {xpath: './/*[not(@class)]'}
297
173
 
298
- expect(selector_builder.build(selector)).to eq built
299
- end
174
+ expect(selector_builder.build(selector)).to eq built
175
+ end
300
176
 
301
- it 'negative' do
302
- selector = {tag_name: 'div', index: -7}
303
- built = {xpath: "(.//*[local-name()='div'])[last()-6]"}
177
+ it 'empty Array finds elements without class' do
178
+ selector = {class_name: []}
179
+ built = {xpath: './/*[not(@class)]'}
304
180
 
305
- expect(selector_builder.build(selector)).to eq built
306
- end
181
+ expect(selector_builder.build(selector)).to eq built
182
+ end
307
183
 
308
- it 'last' do
309
- selector = {tag_name: 'div', index: -1}
310
- built = {xpath: "(.//*[local-name()='div'])[last()]"}
184
+ it 'raises exception when not a String or Regexp or Array' do
185
+ selector = {class: 7}
186
+ msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:Integer/
311
187
 
312
- expect(selector_builder.build(selector)).to eq built
313
- end
188
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
189
+ end
314
190
 
315
- it 'does not return index if it is zero' do
316
- selector = {tag_name: 'div', index: 0}
317
- built = {xpath: ".//*[local-name()='div']"}
191
+ it 'raises exception when Array values are not a String or Regexp' do
192
+ selector = {class: [7]}
193
+ msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:Integer/
318
194
 
319
- expect(selector_builder.build(selector)).to eq built
320
- end
195
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
196
+ end
197
+ end
321
198
 
322
- it 'raises exception when index is not an Integer' do
323
- selector = {index: 'foo'}
324
- msg = /expected one of \[(Integer|Fixnum)\], got "foo":String/
199
+ context 'with attributes as predicates' do
200
+ it 'with href attribute' do
201
+ selector = {href: 'watirspec.css'}
202
+ built = {xpath: ".//*[normalize-space(@href)='watirspec.css']"}
325
203
 
326
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
327
- end
328
- end
204
+ expect(selector_builder.build(selector)).to eq built
205
+ end
329
206
 
330
- context 'with labels' do
331
- it 'locates the element associated with the label element located by the text of the provided label key' do
332
- selector = {label: 'Cars'}
333
- built = {xpath: ".//*[@id=//label[normalize-space()='Cars']/@for " \
334
- "or parent::label[normalize-space()='Cars']]"}
207
+ it 'with String attribute key' do
208
+ selector = {'id' => 'user_new'}
209
+ built = {xpath: ".//*[@id='user_new']"}
335
210
 
336
- expect(selector_builder.build(selector)).to eq built
337
- end
211
+ expect(selector_builder.build(selector)).to eq built
212
+ end
338
213
 
339
- it 'returns a label_element if complex' do
340
- selector = {label: /Ca|rs/}
341
- xpath = './/*[@id=//label[normalize-space()]/@for or parent::label[normalize-space()]]'
342
- built = {xpath: xpath, label_element: /Ca|rs/}
214
+ it 'with String equals' do
215
+ selector = {id: 'user_new'}
216
+ built = {xpath: ".//*[@id='user_new']"}
343
217
 
344
- expect(selector_builder.build(selector)).to eq built
345
- end
218
+ expect(selector_builder.build(selector)).to eq built
219
+ end
346
220
 
347
- it 'returns a visible_label_element if complex' do
348
- selector = {visible_label: /Ca|rs/}
349
- built = {xpath: './/*', visible_label_element: /Ca|rs/}
221
+ it 'with TrueClass no equals' do
222
+ selector = {tag_name: 'input', id: true}
223
+ built = {xpath: ".//*[local-name()='input'][@id]"}
350
224
 
351
- expect(selector_builder.build(selector)).to eq built
352
- end
225
+ expect(selector_builder.build(selector)).to eq built
226
+ end
353
227
 
354
- it 'does not use the label element when label is a valid attribute' do
355
- @attributes ||= Watir::Option.attribute_list
228
+ it 'with FalseClass not with no equals' do
229
+ selector = {tag_name: 'input', name: false}
230
+ built = {xpath: ".//*[local-name()='input'][not(@name)]"}
356
231
 
357
- selector = {tag_name: 'option', label: 'Germany'}
358
- built = {xpath: ".//*[local-name()='option'][@label='Germany']"}
232
+ expect(selector_builder.build(selector)).to eq built
233
+ end
359
234
 
360
- expect(selector_builder.build(selector)).to eq built
361
- end
362
- end
235
+ it 'with multiple attributes: no equals and not with no equals and equals' do
236
+ selector = {readonly: true, foo: false, id: 'good_luck'}
237
+ built = {xpath: ".//*[@readonly and not(@foo) and @id='good_luck']"}
363
238
 
364
- context 'with adjacent locators' do
365
- it 'raises exception when not a Symbol' do
366
- selector = {adjacent: 'foo', index: 0}
367
- msg = 'expected one of [Symbol], got "foo":String'
239
+ expect(selector_builder.build(selector)).to eq built
240
+ end
368
241
 
369
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
370
- end
242
+ it 'raises exception when attribute value is not a Boolean, String or Regexp' do
243
+ selector = {foo: 7}
244
+ msg = /expected one of \[String, Regexp, TrueClass, FalseClass\], got 7:Integer/
371
245
 
372
- it 'raises exception when not a valid value' do
373
- selector = {adjacent: :foo, index: 0}
374
- msg = 'Unable to process adjacent locator with foo'
246
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
247
+ end
375
248
 
376
- expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
377
- end
249
+ it 'raises exception when attribute key is not a String or Regexp' do
250
+ selector = {7 => 'foo'}
251
+ msg = /Unable to build XPath using 7:Integer/
378
252
 
379
- describe '#parent' do
380
- it 'with no other arguments' do
381
- selector = {adjacent: :ancestor, index: 0}
382
- built = {xpath: './ancestor::*[1]'}
253
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
254
+ end
255
+ end
383
256
 
384
- expect(selector_builder.build(selector)).to eq built
385
- end
257
+ context 'with attributes as partials' do
258
+ it 'with Regexp' do
259
+ selector = {name: /user/}
260
+ built = {xpath: './/*[contains(@name, "user")]'}
386
261
 
387
- it 'with index' do
388
- selector = {adjacent: :ancestor, index: 2}
389
- built = {xpath: './ancestor::*[3]'}
262
+ expect(selector_builder.build(selector)).to eq built
263
+ end
390
264
 
391
- expect(selector_builder.build(selector)).to eq built
392
- end
265
+ it 'with multiple Regexp attributes separated by and' do
266
+ selector = {readonly: /read/, id: /good/}
267
+ built = {xpath: './/*[contains(@readonly, "read") and contains(@id, "good")]'}
393
268
 
394
- it 'with multiple locators' do
395
- selector = {adjacent: :ancestor, id: true, tag_name: 'div', class: 'ancestor', index: 1}
396
- built = {xpath: "./ancestor::*[local-name()='div']" \
397
- "[contains(concat(' ', normalize-space(@class), ' '), ' ancestor ')][@id][2]"}
269
+ expect(selector_builder.build(selector)).to eq built
270
+ end
271
+ end
398
272
 
399
- expect(selector_builder.build(selector)).to eq built
400
- end
273
+ context 'with text' do
274
+ it 'String uses normalize space equals' do
275
+ selector = {text: 'Add user'}
276
+ built = {xpath: ".//*[normalize-space()='Add user']"}
401
277
 
402
- it 'raises an exception if text locator is used' do
403
- selector = {adjacent: :ancestor, index: 0, text: 'Foo'}
404
- msg = 'Can not find parent element with text locator'
405
- expect { selector_builder.build(selector) }
406
- .to raise_exception Watir::Exception::LocatorException, msg
407
- end
408
- end
278
+ expect(selector_builder.build(selector)).to eq built
279
+ end
409
280
 
410
- describe '#following_sibling' do
411
- it 'with no other arguments' do
412
- selector = {adjacent: :following, index: 0}
413
- built = {xpath: './following-sibling::*[1]'}
281
+ it 'Regexp uses contains normalize space' do
282
+ selector = {text: /Add/}
283
+ built = {xpath: './/*[contains(normalize-space(), "Add")]'}
414
284
 
415
- expect(selector_builder.build(selector)).to eq built
416
- end
285
+ expect(selector_builder.build(selector)).to eq built
286
+ end
417
287
 
418
- it 'with index' do
419
- selector = {adjacent: :following, index: 2}
420
- built = {xpath: './following-sibling::*[3]'}
288
+ it 'raises exception when text is not a String or Regexp' do
289
+ selector = {text: 7}
290
+ msg = /expected one of \[String, Regexp\], got 7:Integer/
421
291
 
422
- expect(selector_builder.build(selector)).to eq built
423
- end
292
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
293
+ end
294
+ end
424
295
 
425
- it 'with multiple locators' do
426
- selector = {adjacent: :following, tag_name: 'div', class: 'b', index: 0, id: true}
427
- built = {xpath: "./following-sibling::*[local-name()='div']" \
428
- "[contains(concat(' ', normalize-space(@class), ' '), ' b ')][@id][1]"}
296
+ context 'with index' do
297
+ it 'positive' do
298
+ selector = {tag_name: 'div', index: 7}
299
+ built = {xpath: "(.//*[local-name()='div'])[8]"}
429
300
 
430
- expect(selector_builder.build(selector)).to eq built
431
- end
301
+ expect(selector_builder.build(selector)).to eq built
302
+ end
432
303
 
433
- it 'with text' do
434
- selector = {adjacent: :following, text: 'Third', index: 0}
435
- built = {xpath: "./following-sibling::*[normalize-space()='Third'][1]"}
304
+ it 'negative' do
305
+ selector = {tag_name: 'div', index: -7}
306
+ built = {xpath: "(.//*[local-name()='div'])[last()-6]"}
436
307
 
437
- expect(selector_builder.build(selector)).to eq built
438
- end
439
- end
308
+ expect(selector_builder.build(selector)).to eq built
309
+ end
440
310
 
441
- describe '#previous_sibling' do
442
- it 'with no other arguments' do
443
- selector = {adjacent: :preceding, index: 0}
444
- built = {xpath: './preceding-sibling::*[1]'}
311
+ it 'last' do
312
+ selector = {tag_name: 'div', index: -1}
313
+ built = {xpath: "(.//*[local-name()='div'])[last()]"}
445
314
 
446
- expect(selector_builder.build(selector)).to eq built
447
- end
315
+ expect(selector_builder.build(selector)).to eq built
316
+ end
448
317
 
449
- it 'with index' do
450
- selector = {adjacent: :preceding, index: 2}
451
- built = {xpath: './preceding-sibling::*[3]'}
318
+ it 'does not return index if it is zero' do
319
+ selector = {tag_name: 'div', index: 0}
320
+ built = {xpath: ".//*[local-name()='div']"}
452
321
 
453
- expect(selector_builder.build(selector)).to eq built
454
- end
322
+ expect(selector_builder.build(selector)).to eq built
323
+ end
455
324
 
456
- it 'with multiple locators' do
457
- selector = {adjacent: :preceding, tag_name: 'div', class: 'b', id: true, index: 0}
458
- built = {xpath: "./preceding-sibling::*[local-name()='div']" \
459
- "[contains(concat(' ', normalize-space(@class), ' '), ' b ')][@id][1]"}
325
+ it 'raises exception when index is not an Integer' do
326
+ selector = {index: 'foo'}
327
+ msg = /expected one of \[(Integer|Fixnum)\], got "foo":String/
460
328
 
461
- expect(selector_builder.build(selector)).to eq built
462
- end
329
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
330
+ end
331
+ end
463
332
 
464
- it 'with text' do
465
- selector = {adjacent: :preceding, text: 'Second', index: 0}
466
- built = {xpath: "./preceding-sibling::*[normalize-space()='Second'][1]"}
333
+ context 'with labels' do
334
+ it 'locates the element associated with the label element located by the text of the provided label key' do
335
+ selector = {label: 'Cars'}
336
+ built = {xpath: ".//*[@id=//label[normalize-space()='Cars']/@for " \
337
+ "or parent::label[normalize-space()='Cars']]"}
467
338
 
468
- expect(selector_builder.build(selector)).to eq built
469
- end
470
- end
339
+ expect(selector_builder.build(selector)).to eq built
340
+ end
471
341
 
472
- describe '#child' do
473
- it 'with no other arguments' do
474
- selector = {adjacent: :child, index: 0}
475
- built = {xpath: './child::*[1]'}
342
+ it 'returns a label_element if complex' do
343
+ selector = {label: /Ca|rs/}
344
+ xpath = './/*[@id=//label[normalize-space()]/@for or parent::label[normalize-space()]]'
345
+ built = {xpath: xpath, label_element: /Ca|rs/}
476
346
 
477
- expect(selector_builder.build(selector)).to eq built
478
- end
347
+ expect(selector_builder.build(selector)).to eq built
348
+ end
479
349
 
480
- it 'with index' do
481
- selector = {adjacent: :child, index: 2}
482
- built = {xpath: './child::*[3]'}
350
+ it 'returns a visible_label_element if complex' do
351
+ selector = {visible_label: /Ca|rs/}
352
+ built = {xpath: './/*', visible_label_element: /Ca|rs/}
483
353
 
484
- expect(selector_builder.build(selector)).to eq built
485
- end
354
+ expect(selector_builder.build(selector)).to eq built
355
+ end
486
356
 
487
- it 'with multiple locators' do
488
- selector = {adjacent: :child, tag_name: 'div', class: 'b', id: true, index: 0}
489
- built = {xpath: "./child::*[local-name()='div']" \
490
- "[contains(concat(' ', normalize-space(@class), ' '), ' b ')][@id][1]"}
357
+ it 'does not use the label element when label is a valid attribute' do
358
+ @attributes ||= Watir::Option.attribute_list
491
359
 
492
- expect(selector_builder.build(selector)).to eq built
493
- end
360
+ selector = {tag_name: 'option', label: 'Germany'}
361
+ built = {xpath: ".//*[local-name()='option'][@label='Germany']"}
494
362
 
495
- it 'with text' do
496
- selector = {adjacent: :child, text: 'Second', index: 0}
497
- built = {xpath: "./child::*[normalize-space()='Second'][1]"}
363
+ expect(selector_builder.build(selector)).to eq built
364
+ end
365
+ end
498
366
 
499
- expect(selector_builder.build(selector)).to eq built
500
- end
501
- end
502
- end
367
+ context 'with adjacent locators' do
368
+ it 'raises exception when not a Symbol' do
369
+ selector = {adjacent: 'foo', index: 0}
370
+ msg = 'expected one of [Symbol], got "foo":String'
503
371
 
504
- context 'with multiple locators' do
505
- it 'locates using tag name, class, attributes and text' do
506
- selector = {tag_name: 'div', class: 'content', contenteditable: 'true', text: 'Foo'}
507
- built = {xpath: ".//*[local-name()='div'][contains(concat(' ', normalize-space(@class), ' '), ' content ')]" \
508
- "[normalize-space()='Foo'][@contenteditable='true']"}
372
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
373
+ end
509
374
 
510
- expect(selector_builder.build(selector)).to eq built
511
- end
512
- end
375
+ it 'raises exception when not a valid value' do
376
+ selector = {adjacent: :foo, index: 0}
377
+ msg = 'Unable to process adjacent locator with foo'
513
378
 
514
- context 'with simple Regexp' do
515
- it 'handles spaces' do
516
- selector = {title: /od Lu/}
517
- built = {xpath: './/*[contains(@title, "od Lu")]'}
379
+ expect { selector_builder.build(selector) }.to raise_exception Watir::Exception::LocatorException, msg
380
+ end
518
381
 
519
- expect(selector_builder.build(selector)).to eq built
520
- end
382
+ describe '#parent' do
383
+ it 'with no other arguments' do
384
+ selector = {adjacent: :ancestor, index: 0}
385
+ built = {xpath: './ancestor::*[1]'}
521
386
 
522
- it 'handles escaped characters' do
523
- selector = {src: %r{ages/but}}
524
- built = {xpath: './/*[contains(@src, "ages/but")]'}
387
+ expect(selector_builder.build(selector)).to eq built
388
+ end
525
389
 
526
- expect(selector_builder.build(selector)).to eq built
527
- end
528
- end
390
+ it 'with index' do
391
+ selector = {adjacent: :ancestor, index: 2}
392
+ built = {xpath: './ancestor::*[3]'}
529
393
 
530
- context 'with complex Regexp' do
531
- it 'handles wildcards' do
532
- selector = {src: /ages.*but/}
533
- built = {xpath: './/*[contains(@src, "ages") and contains(@src, "but")]', src: /ages.*but/}
394
+ expect(selector_builder.build(selector)).to eq built
395
+ end
534
396
 
535
- expect(selector_builder.build(selector)).to eq built
536
- end
397
+ it 'with multiple locators' do
398
+ selector = {adjacent: :ancestor, id: true, tag_name: 'div', class: 'ancestor', index: 1}
399
+ built = {xpath: "./ancestor::*[local-name()='div']" \
400
+ "[contains(concat(' ', normalize-space(@class), ' '), ' ancestor ')][@id][2]"}
537
401
 
538
- it 'handles optional characters' do
539
- selector = {src: /ages ?but/}
540
- built = {xpath: './/*[contains(@src, "ages") and contains(@src, "but")]', src: /ages ?but/}
402
+ expect(selector_builder.build(selector)).to eq built
403
+ end
541
404
 
542
- expect(selector_builder.build(selector)).to eq built
543
- end
405
+ it 'raises an exception if text locator is used' do
406
+ selector = {adjacent: :ancestor, index: 0, text: 'Foo'}
407
+ msg = 'Can not find parent element with text locator'
408
+ expect { selector_builder.build(selector) }
409
+ .to raise_exception Watir::Exception::LocatorException, msg
410
+ end
411
+ end
544
412
 
545
- it 'handles anchors' do
546
- selector = {name: /^new_user_image$/}
547
- built = {xpath: './/*[contains(@name, "new_user_image")]', name: /^new_user_image$/}
413
+ describe '#following_sibling' do
414
+ it 'with no other arguments' do
415
+ selector = {adjacent: :following, index: 0}
416
+ built = {xpath: './following-sibling::*[1]'}
548
417
 
549
- expect(selector_builder.build(selector)).to eq built
550
- end
418
+ expect(selector_builder.build(selector)).to eq built
419
+ end
551
420
 
552
- it 'handles beginning anchor' do
553
- selector = {src: /^i/}
554
- built = {xpath: ".//*[starts-with(@src, 'i')]"}
421
+ it 'with index' do
422
+ selector = {adjacent: :following, index: 2}
423
+ built = {xpath: './following-sibling::*[3]'}
555
424
 
556
- expect(selector_builder.build(selector)).to eq built
557
- end
425
+ expect(selector_builder.build(selector)).to eq built
426
+ end
558
427
 
559
- it 'does not use starts-with if visible locator used' do
560
- selector = {id: /^vis/, visible_text: 'shown div'}
561
- built = {xpath: './/*[contains(@id, "vis")]', id: /^vis/, visible_text: 'shown div'}
428
+ it 'with multiple locators' do
429
+ selector = {adjacent: :following, tag_name: 'div', class: 'b', index: 0, id: true}
430
+ built = {xpath: "./following-sibling::*[local-name()='div']" \
431
+ "[contains(concat(' ', normalize-space(@class), ' '), ' b ')][@id][1]"}
562
432
 
563
- expect(selector_builder.build(selector)).to eq built
564
- end
433
+ expect(selector_builder.build(selector)).to eq built
434
+ end
565
435
 
566
- it 'handles case insensitive' do
567
- selector = {action: /ME/i}
568
- built = {xpath: './/*[contains(translate(@action,' \
569
- "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
570
- "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'), " \
571
- 'translate("me",' \
572
- "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
573
- "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'))]"}
436
+ it 'with text' do
437
+ selector = {adjacent: :following, text: 'Third', index: 0}
438
+ built = {xpath: "./following-sibling::*[normalize-space()='Third'][1]"}
574
439
 
575
- expect(selector_builder.build(selector)).to eq built
576
- end
577
- end
440
+ expect(selector_builder.build(selector)).to eq built
441
+ end
442
+ end
578
443
 
579
- context 'with special cased selectors' do
580
- it 'handles data-* attributes with String' do
581
- selector = {data_foo: 'user_new'}
582
- built = {xpath: ".//*[@data-foo='user_new']"}
444
+ describe '#previous_sibling' do
445
+ it 'with no other arguments' do
446
+ selector = {adjacent: :preceding, index: 0}
447
+ built = {xpath: './preceding-sibling::*[1]'}
583
448
 
584
- expect(selector_builder.build(selector)).to eq built
585
- end
449
+ expect(selector_builder.build(selector)).to eq built
450
+ end
586
451
 
587
- it 'handles aria-* attributes' do
588
- selector = {aria_foo: 'user_new'}
589
- built = {xpath: ".//*[@aria-foo='user_new']"}
452
+ it 'with index' do
453
+ selector = {adjacent: :preceding, index: 2}
454
+ built = {xpath: './preceding-sibling::*[3]'}
590
455
 
591
- expect(selector_builder.build(selector)).to eq built
592
- end
456
+ expect(selector_builder.build(selector)).to eq built
457
+ end
593
458
 
594
- it "doesn't modify attribute name when the attribute key is a string" do
595
- selector = {'_underscore-dash' => 'user_new'}
596
- built = {xpath: ".//*[@_underscore-dash='user_new']"}
459
+ it 'with multiple locators' do
460
+ selector = {adjacent: :preceding, tag_name: 'div', class: 'b', id: true, index: 0}
461
+ built = {xpath: "./preceding-sibling::*[local-name()='div']" \
462
+ "[contains(concat(' ', normalize-space(@class), ' '), ' b ')][@id][1]"}
597
463
 
598
- expect(selector_builder.build(selector)).to eq built
599
- end
464
+ expect(selector_builder.build(selector)).to eq built
465
+ end
600
466
 
601
- it 'translates ruby attribute names to content attribute names' do
602
- selector = {http_equiv: 'foo'}
603
- built = {xpath: ".//*[@http-equiv='foo']"}
467
+ it 'with text' do
468
+ selector = {adjacent: :preceding, text: 'Second', index: 0}
469
+ built = {xpath: "./preceding-sibling::*[normalize-space()='Second'][1]"}
604
470
 
605
- expect(selector_builder.build(selector)).to eq built
606
- end
607
- end
471
+ expect(selector_builder.build(selector)).to eq built
472
+ end
473
+ end
608
474
 
609
- context 'returns locators that can not be directly translated' do
610
- it 'attribute with complicated Regexp at end' do
611
- selector = {action: /me$/}
612
- built = {xpath: './/*[contains(@action, "me")]', action: /me$/}
475
+ describe '#child' do
476
+ it 'with no other arguments' do
477
+ selector = {adjacent: :child, index: 0}
478
+ built = {xpath: './child::*[1]'}
479
+
480
+ expect(selector_builder.build(selector)).to eq built
481
+ end
482
+
483
+ it 'with index' do
484
+ selector = {adjacent: :child, index: 2}
485
+ built = {xpath: './child::*[3]'}
486
+
487
+ expect(selector_builder.build(selector)).to eq built
488
+ end
489
+
490
+ it 'with multiple locators' do
491
+ selector = {adjacent: :child, tag_name: 'div', class: 'b', id: true, index: 0}
492
+ built = {xpath: "./child::*[local-name()='div']" \
493
+ "[contains(concat(' ', normalize-space(@class), ' '), ' b ')][@id][1]"}
494
+
495
+ expect(selector_builder.build(selector)).to eq built
496
+ end
497
+
498
+ it 'with text' do
499
+ selector = {adjacent: :child, text: 'Second', index: 0}
500
+ built = {xpath: "./child::*[normalize-space()='Second'][1]"}
613
501
 
614
- expect(selector_builder.build(selector)).to eq built
615
- end
502
+ expect(selector_builder.build(selector)).to eq built
503
+ end
504
+ end
505
+ end
506
+
507
+ context 'with multiple locators' do
508
+ it 'locates using tag name, class, attributes and text' do
509
+ selector = {tag_name: 'div', class: 'content', contenteditable: 'true', text: 'Foo'}
510
+ built = {xpath: ".//*[local-name()='div']" \
511
+ "[contains(concat(' ', normalize-space(@class), ' '), ' content ')]" \
512
+ "[normalize-space()='Foo'][@contenteditable='true']"}
616
513
 
617
- it 'class with complicated Regexp' do
618
- selector = {class: /he?r/}
619
- built = {xpath: './/*[contains(@class, "h") and contains(@class, "r")]', class: [/he?r/]}
514
+ expect(selector_builder.build(selector)).to eq built
515
+ end
516
+ end
620
517
 
621
- expect(selector_builder.build(selector)).to eq built
622
- end
518
+ context 'with simple Regexp' do
519
+ it 'handles spaces' do
520
+ selector = {title: /od Lu/}
521
+ built = {xpath: './/*[contains(@title, "od Lu")]'}
623
522
 
624
- it 'visible' do
625
- selector = {tag_name: 'div', visible: true}
626
- built = {xpath: ".//*[local-name()='div']", visible: true}
523
+ expect(selector_builder.build(selector)).to eq built
524
+ end
627
525
 
628
- expect(selector_builder.build(selector)).to eq built
629
- end
526
+ it 'handles escaped characters' do
527
+ selector = {src: %r{ages/but}}
528
+ built = {xpath: './/*[contains(@src, "ages/but")]'}
630
529
 
631
- it 'not visible' do
632
- selector = {tag_name: 'span', visible: false}
633
- built = {xpath: ".//*[local-name()='span']", visible: false}
530
+ expect(selector_builder.build(selector)).to eq built
531
+ end
532
+ end
634
533
 
635
- expect(selector_builder.build(selector)).to eq built
636
- end
534
+ context 'with complex Regexp' do
535
+ it 'handles wildcards' do
536
+ selector = {src: /ages.*but/}
537
+ built = {xpath: './/*[contains(@src, "ages") and contains(@src, "but")]', src: /ages.*but/}
637
538
 
638
- it 'visible text' do
639
- selector = {tag_name: 'span', visible_text: 'foo'}
640
- built = {xpath: ".//*[local-name()='span']", visible_text: 'foo'}
539
+ expect(selector_builder.build(selector)).to eq built
540
+ end
641
541
 
642
- expect(selector_builder.build(selector)).to eq built
643
- end
542
+ it 'handles optional characters' do
543
+ selector = {src: /ages ?but/}
544
+ built = {xpath: './/*[contains(@src, "ages") and contains(@src, "but")]', src: /ages ?but/}
644
545
 
645
- it 'raises exception when visible is not boolean' do
646
- selector = {visible: 'foo'}
647
- msg = 'expected one of [TrueClass, FalseClass], got "foo":String'
546
+ expect(selector_builder.build(selector)).to eq built
547
+ end
648
548
 
649
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
650
- end
549
+ it 'handles anchors' do
550
+ selector = {name: /^new_user_image$/}
551
+ built = {xpath: './/*[contains(@name, "new_user_image")]', name: /^new_user_image$/}
651
552
 
652
- it 'raises exception when visible text is not a String or Regexp' do
653
- selector = {visible_text: 7}
654
- msg = /expected one of \[String, Regexp\], got 7:Integer/
553
+ expect(selector_builder.build(selector)).to eq built
554
+ end
655
555
 
656
- expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
657
- end
658
- end
556
+ it 'handles beginning anchor' do
557
+ selector = {src: /^i/}
558
+ built = {xpath: ".//*[starts-with(@src, 'i')]"}
659
559
 
660
- context 'with generic element scope' do
661
- let(:query_scope) { instance_double Watir::HTMLElement }
662
- let(:scope_built) { {xpath: ".//*[local-name()='div'][@id='table-rows-test']"} }
560
+ expect(selector_builder.build(selector)).to eq built
561
+ end
663
562
 
664
- before do
665
- allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
666
- allow(query_scope).to receive(:browser).and_return(browser)
667
- allow(selector_builder).to receive(:built).and_return(scope_built)
668
- end
563
+ it 'does not use starts-with if visible locator used' do
564
+ selector = {id: /^vis/, visible_text: 'shown div'}
565
+ built = {xpath: './/*[contains(@id, "vis")]', id: /^vis/, visible_text: 'shown div'}
669
566
 
670
- it 'uses scope' do
671
- selector = {tag_name: 'div'}
672
- built = {xpath: "(#{scope_built[:xpath]})[1]//*[local-name()='div']"}
567
+ expect(selector_builder.build(selector)).to eq built
568
+ end
673
569
 
674
- expect(selector_builder.build(selector)).to eq built
675
- end
570
+ it 'handles case insensitive' do
571
+ selector = {action: /ME/i}
572
+ built = {xpath: './/*[contains(translate(@action,' \
573
+ "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
574
+ "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'), " \
575
+ 'translate("me",' \
576
+ "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'," \
577
+ "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'))]"}
676
578
 
677
- it 'does not use scope if selector is a CSS' do
678
- selector = {css: 'div'}
579
+ expect(selector_builder.build(selector)).to eq built
580
+ end
581
+ end
679
582
 
680
- build_selector = selector_builder.build(selector)
681
- expect(build_selector.delete(:scope)).to_not be_nil
682
- expect(build_selector).to eq selector
683
- end
583
+ context 'with special cased selectors' do
584
+ it 'handles data-* attributes with String' do
585
+ selector = {data_foo: 'user_new'}
586
+ built = {xpath: ".//*[@data-foo='user_new']"}
684
587
 
685
- it 'does not use scope if selector is a XPath' do
686
- selector = {xpath: './/*'}
588
+ expect(selector_builder.build(selector)).to eq built
589
+ end
687
590
 
688
- build_selector = selector_builder.build(selector)
689
- expect(build_selector.delete(:scope)).to_not be_nil
690
- expect(build_selector).to eq selector
691
- end
591
+ it 'handles aria-* attributes' do
592
+ selector = {aria_foo: 'user_new'}
593
+ built = {xpath: ".//*[@aria-foo='user_new']"}
692
594
 
693
- it 'does not use scope if selector has :adjacent' do
694
- selector = {adjacent: :ancestor, index: 0}
695
- built = {xpath: './ancestor::*[1]'}
595
+ expect(selector_builder.build(selector)).to eq built
596
+ end
696
597
 
697
- build_selector = selector_builder.build(selector)
698
- expect(build_selector.delete(:scope)).to_not be_nil
699
- expect(build_selector).to eq built
700
- end
701
- end
598
+ it "doesn't modify attribute name when the attribute key is a string" do
599
+ selector = {'_underscore-dash' => 'user_new'}
600
+ built = {xpath: ".//*[@_underscore-dash='user_new']"}
702
601
 
703
- context 'with invalid query scopes' do
704
- let(:query_scope) { instance_double Watir::HTMLElement }
602
+ expect(selector_builder.build(selector)).to eq built
603
+ end
705
604
 
706
- it 'does not use scope if query_scope built has multiple keys' do
707
- scope_built = {xpath: ".//*[local-name()='div']", visible: true}
708
- allow(selector_builder).to receive(:built).and_return(scope_built)
709
- allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
605
+ it 'translates ruby attribute names to content attribute names' do
606
+ selector = {http_equiv: 'foo'}
607
+ built = {xpath: ".//*[@http-equiv='foo']"}
710
608
 
711
- selector = {tag_name: 'div'}
712
- built = {xpath: ".//*[local-name()='div']"}
609
+ expect(selector_builder.build(selector)).to eq built
610
+ end
611
+ end
713
612
 
714
- build_selector = selector_builder.build(selector)
715
- expect(build_selector.delete(:scope)).to_not be_nil
716
- expect(build_selector).to eq built
717
- end
613
+ context 'when can not be directly translated' do
614
+ it 'returns locator from attribute with complicated Regexp at end' do
615
+ selector = {action: /me$/}
616
+ built = {xpath: './/*[contains(@action, "me")]', action: /me$/}
718
617
 
719
- it 'does not use scope if query_scope uses different Selenium Locator' do
720
- scope_built = {css: '#foo'}
721
- allow(selector_builder).to receive(:built).and_return(scope_built)
722
- allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
618
+ expect(selector_builder.build(selector)).to eq built
619
+ end
723
620
 
724
- selector = {tag_name: 'div'}
725
- built = {xpath: ".//*[local-name()='div']"}
621
+ it 'returns locator from class with complicated Regexp' do
622
+ selector = {class: /he?r/}
623
+ built = {xpath: './/*[contains(@class, "h") and contains(@class, "r")]', class: [/he?r/]}
726
624
 
727
- build_selector = selector_builder.build(selector)
728
- expect(build_selector.delete(:scope)).to_not be_nil
729
- expect(build_selector).to eq built
730
- end
731
- end
625
+ expect(selector_builder.build(selector)).to eq built
626
+ end
732
627
 
733
- context 'with specific element scope' do
734
- let(:scope_built) { {xpath: ".//*[local-name()='iframe'][@id='one']"} }
628
+ it 'returns locator from visible' do
629
+ selector = {tag_name: 'div', visible: true}
630
+ built = {xpath: ".//*[local-name()='div']", visible: true}
735
631
 
736
- it 'does not use scope if query scope is an IFrame' do
737
- query_scope = instance_double Watir::IFrame
738
- selector_builder = described_class.new(attributes, query_scope)
632
+ expect(selector_builder.build(selector)).to eq built
633
+ end
739
634
 
740
- allow(selector_builder).to receive(:built).and_return(scope_built)
741
- allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
742
- allow(query_scope).to receive(:browser).and_return(browser)
743
- allow(query_scope).to receive(:is_a?).with(Watir::Browser).and_return(false)
744
- allow(query_scope).to receive(:is_a?).with(Watir::ShadowRoot).and_return(false)
745
- allow(query_scope).to receive(:is_a?).with(Watir::IFrame).and_return(true)
635
+ it 'returns locator from not visible' do
636
+ selector = {tag_name: 'span', visible: false}
637
+ built = {xpath: ".//*[local-name()='span']", visible: false}
746
638
 
747
- selector = {tag_name: 'div'}
748
- built = {xpath: ".//*[local-name()='div']"}
639
+ expect(selector_builder.build(selector)).to eq built
640
+ end
749
641
 
750
- build_selector = selector_builder.build(selector)
751
- expect(build_selector.delete(:scope)).to_not be_nil
752
- expect(build_selector).to eq built
753
- end
754
- end
642
+ it 'returns locator from visible text' do
643
+ selector = {tag_name: 'span', visible_text: 'foo'}
644
+ built = {xpath: ".//*[local-name()='span']", visible_text: 'foo'}
755
645
 
756
- context 'with case-insensitive attributes' do
757
- let(:upper) { "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'" }
758
- let(:lower) { "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'" }
646
+ expect(selector_builder.build(selector)).to eq built
647
+ end
759
648
 
760
- it 'respects case when locating uknown element with uknown attribute' do
761
- expect(selector_builder.build(hreflang: 'en')).to eq(xpath: ".//*[@hreflang='en']")
762
- expect(selector_builder.build(hreflang: /en/)).to eq(xpath: './/*[contains(@hreflang, "en")]')
763
- end
649
+ it 'raises exception when visible is not boolean' do
650
+ selector = {visible: 'foo'}
651
+ msg = 'expected one of [TrueClass, FalseClass], got "foo":String'
764
652
 
765
- it 'ignores case when locating uknown element with defined attribute' do
766
- lhs = "translate(@lang,#{upper},#{lower})"
767
- rhs = "translate('en',#{upper},#{lower})"
768
- rhs_regex = "translate(\"en\",#{upper},#{lower})"
769
- expect(selector_builder.build(lang: 'en')).to eq(xpath: ".//*[#{lhs}=#{rhs}]")
770
- expect(selector_builder.build(lang: /en/)).to eq(xpath: ".//*[contains(#{lhs}, #{rhs_regex})]")
771
- expect(selector_builder.build(tag_name: /a/, lang: 'en'))
772
- .to eq(xpath: ".//*[contains(local-name(), \"a\")][#{lhs}=#{rhs}]")
773
- expect(selector_builder.build(tag_name: /a/, lang: /en/))
774
- .to eq(xpath: ".//*[contains(local-name(), \"a\")][contains(#{lhs}, #{rhs_regex})]")
775
- end
653
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
654
+ end
776
655
 
777
- it 'ignores case when attribute is defined for element' do
778
- lhs = "translate(@hreflang,#{upper},#{lower})"
779
- rhs = "translate('en',#{upper},#{lower})"
780
- rhs_regex = "translate(\"en\",#{upper},#{lower})"
781
- expect(selector_builder.build(tag_name: 'a', hreflang: 'en'))
782
- .to eq(xpath: ".//*[local-name()='a'][#{lhs}=#{rhs}]")
783
- expect(selector_builder.build(tag_name: 'a', hreflang: /en/))
784
- .to eq(xpath: ".//*[local-name()='a'][contains(#{lhs}, #{rhs_regex})]")
785
- end
656
+ it 'raises exception when visible text is not a String or Regexp' do
657
+ selector = {visible_text: 7}
658
+ msg = /expected one of \[String, Regexp\], got 7:Integer/
786
659
 
787
- it 'respects case when attribute is not defined for element' do
788
- expect(selector_builder.build(tag_name: 'table', hreflang: 'en'))
789
- .to eq(xpath: ".//*[local-name()='table'][@hreflang='en']")
790
- expect(selector_builder.build(tag_name: 'table', hreflang: /en/))
791
- .to eq(xpath: ".//*[local-name()='table'][contains(@hreflang, \"en\")]")
792
- expect(selector_builder.build(tag_name: /a/, hreflang: 'en'))
793
- .to eq(xpath: ".//*[contains(local-name(), \"a\")][@hreflang='en']")
794
- expect(selector_builder.build(tag_name: /a/, hreflang: /en/))
795
- .to eq(xpath: './/*[contains(local-name(), "a")][contains(@hreflang, "en")]')
660
+ expect { selector_builder.build(selector) }.to raise_exception TypeError, msg
661
+ end
662
+ end
663
+
664
+ context 'with generic element scope' do
665
+ let(:query_scope) { instance_double Watir::HTMLElement }
666
+ let(:scope_built) { {xpath: ".//*[local-name()='div'][@id='table-rows-test']"} }
667
+
668
+ before do
669
+ allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
670
+ allow(query_scope).to receive(:browser).and_return(browser)
671
+ allow(selector_builder).to receive(:built).and_return(scope_built)
672
+ end
673
+
674
+ it 'uses scope' do
675
+ selector = {tag_name: 'div'}
676
+ built = {xpath: "(#{scope_built[:xpath]})[1]//*[local-name()='div']"}
677
+
678
+ expect(selector_builder.build(selector)).to eq built
679
+ end
680
+
681
+ it 'does not use scope if selector is a CSS' do
682
+ selector = {css: 'div'}
683
+
684
+ build_selector = selector_builder.build(selector)
685
+ expect(build_selector.delete(:scope)).not_to be_nil
686
+ expect(build_selector).to eq selector
687
+ end
688
+
689
+ it 'does not use scope if selector is a XPath' do
690
+ selector = {xpath: './/*'}
691
+
692
+ build_selector = selector_builder.build(selector)
693
+ expect(build_selector.delete(:scope)).not_to be_nil
694
+ expect(build_selector).to eq selector
695
+ end
696
+
697
+ it 'does not use scope if selector has :adjacent' do
698
+ selector = {adjacent: :ancestor, index: 0}
699
+ built = {xpath: './ancestor::*[1]'}
700
+
701
+ build_selector = selector_builder.build(selector)
702
+ expect(build_selector.delete(:scope)).not_to be_nil
703
+ expect(build_selector).to eq built
704
+ end
705
+ end
706
+
707
+ context 'with invalid query scopes' do
708
+ let(:query_scope) { instance_double Watir::HTMLElement }
709
+
710
+ it 'does not use scope if query_scope built has multiple keys' do
711
+ scope_built = {xpath: ".//*[local-name()='div']", visible: true}
712
+ allow(selector_builder).to receive(:built).and_return(scope_built)
713
+ allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
714
+
715
+ selector = {tag_name: 'div'}
716
+ built = {xpath: ".//*[local-name()='div']"}
717
+
718
+ build_selector = selector_builder.build(selector)
719
+ expect(build_selector.delete(:scope)).not_to be_nil
720
+ expect(build_selector).to eq built
721
+ end
722
+
723
+ it 'does not use scope if query_scope uses different Selenium Locator' do
724
+ scope_built = {css: '#foo'}
725
+ allow(selector_builder).to receive(:built).and_return(scope_built)
726
+ allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
727
+
728
+ selector = {tag_name: 'div'}
729
+ built = {xpath: ".//*[local-name()='div']"}
730
+
731
+ build_selector = selector_builder.build(selector)
732
+ expect(build_selector.delete(:scope)).not_to be_nil
733
+ expect(build_selector).to eq built
734
+ end
735
+ end
736
+
737
+ context 'with specific element scope' do
738
+ let(:scope_built) { {xpath: ".//*[local-name()='iframe'][@id='one']"} }
739
+
740
+ it 'does not use scope if query scope is an IFrame' do
741
+ query_scope = instance_double Watir::IFrame
742
+ selector_builder = described_class.new(attributes, query_scope)
743
+
744
+ allow(selector_builder).to receive(:built).and_return(scope_built)
745
+ allow(query_scope).to receive(:selector_builder).and_return(selector_builder)
746
+ allow(query_scope).to receive(:browser).and_return(browser)
747
+ allow(query_scope).to receive(:is_a?).with(Watir::Browser).and_return(false)
748
+ allow(query_scope).to receive(:is_a?).with(Watir::ShadowRoot).and_return(false)
749
+ allow(query_scope).to receive(:is_a?).with(Watir::IFrame).and_return(true)
750
+
751
+ selector = {tag_name: 'div'}
752
+ built = {xpath: ".//*[local-name()='div']"}
753
+
754
+ build_selector = selector_builder.build(selector)
755
+ expect(build_selector.delete(:scope)).not_to be_nil
756
+ expect(build_selector).to eq built
757
+ end
758
+ end
759
+
760
+ context 'with case-insensitive attributes' do
761
+ let(:upper) { "'ABCDEFGHIJKLMNOPQRSTUVWXYZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞŸŽŠŒ'" }
762
+ let(:lower) { "'abcdefghijklmnopqrstuvwxyzàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿžšœ'" }
763
+
764
+ it 'respects case when locating uknown element with uknown attribute' do
765
+ expect(selector_builder.build(hreflang: 'en')).to eq(xpath: ".//*[@hreflang='en']")
766
+ expect(selector_builder.build(hreflang: /en/)).to eq(xpath: './/*[contains(@hreflang, "en")]')
767
+ end
768
+
769
+ it 'ignores case when locating uknown element with defined attribute' do
770
+ lhs = "translate(@lang,#{upper},#{lower})"
771
+ rhs = "translate('en',#{upper},#{lower})"
772
+ rhs_regex = "translate(\"en\",#{upper},#{lower})"
773
+ expect(selector_builder.build(lang: 'en')).to eq(xpath: ".//*[#{lhs}=#{rhs}]")
774
+ expect(selector_builder.build(lang: /en/)).to eq(xpath: ".//*[contains(#{lhs}, #{rhs_regex})]")
775
+ expect(selector_builder.build(tag_name: /a/, lang: 'en'))
776
+ .to eq(xpath: ".//*[contains(local-name(), \"a\")][#{lhs}=#{rhs}]")
777
+ expect(selector_builder.build(tag_name: /a/, lang: /en/))
778
+ .to eq(xpath: ".//*[contains(local-name(), \"a\")][contains(#{lhs}, #{rhs_regex})]")
779
+ end
780
+
781
+ it 'ignores case when attribute is defined for element' do
782
+ lhs = "translate(@hreflang,#{upper},#{lower})"
783
+ rhs = "translate('en',#{upper},#{lower})"
784
+ rhs_regex = "translate(\"en\",#{upper},#{lower})"
785
+ expect(selector_builder.build(tag_name: 'a', hreflang: 'en'))
786
+ .to eq(xpath: ".//*[local-name()='a'][#{lhs}=#{rhs}]")
787
+ expect(selector_builder.build(tag_name: 'a', hreflang: /en/))
788
+ .to eq(xpath: ".//*[local-name()='a'][contains(#{lhs}, #{rhs_regex})]")
789
+ end
790
+
791
+ it 'respects case when attribute is not defined for element' do
792
+ expect(selector_builder.build(tag_name: 'table', hreflang: 'en'))
793
+ .to eq(xpath: ".//*[local-name()='table'][@hreflang='en']")
794
+ expect(selector_builder.build(tag_name: 'table', hreflang: /en/))
795
+ .to eq(xpath: ".//*[local-name()='table'][contains(@hreflang, \"en\")]")
796
+ expect(selector_builder.build(tag_name: /a/, hreflang: 'en'))
797
+ .to eq(xpath: ".//*[contains(local-name(), \"a\")][@hreflang='en']")
798
+ expect(selector_builder.build(tag_name: /a/, hreflang: /en/))
799
+ .to eq(xpath: './/*[contains(local-name(), "a")][contains(@hreflang, "en")]')
800
+ end
801
+ end
802
+ end
796
803
  end
797
804
  end
798
805
  end