Skip to content

Commit b4b8aab

Browse files
syber911911syber911911VietND96
authored
[java] feat: Add method to select options containing the provided text (#14426)
--------- Signed-off-by: Viet Nguyen Duc <[email protected]> Co-authored-by: syber911911 <[email protected]> Co-authored-by: Viet Nguyen Duc <[email protected]>
1 parent 19a4546 commit b4b8aab

File tree

5 files changed

+256
-3
lines changed

5 files changed

+256
-3
lines changed

‎common/src/web/formPage.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@
8080
<option value="oranges">Oranges</option>
8181
</select>
8282

83+
<select id="invisible_multi_select" multiple>
84+
<option selected="selected" value="apples" style="opacity: 0;">Apples</option>
85+
<option value="oranges">Oranges</option>
86+
<option selected="selected" value="lemons">Lemons</option>
87+
</select>
88+
8389
<select name="select-default">
8490
<option>One</option>
8591
<option>Two</option>

‎java/src/org/openqa/selenium/support/ui/ISelect.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@ public interface ISelect {
6060
*/
6161
void selectByVisibleText(String text);
6262

63+
/**
64+
* Select all options that display text matching or containing the argument. That is, when given
65+
* "Bar" this would select an option like:
66+
*
67+
* <p>&lt;option value="foo"&gt;Bar&lt;/option&gt;
68+
*
69+
* <p>Additionally, if no exact match is found, this will attempt to select options that contain
70+
* the argument as a substring. For example, when given "1년", this would select an option like:
71+
*
72+
* <p>&lt;option value="bar"&gt;1년납&lt;/option&gt;
73+
*
74+
* @param text The visible text to match or partially match against
75+
*/
76+
void selectByContainsVisibleText(String text);
77+
6378
/**
6479
* Select the option at the given index. This is done by examining the "index" attribute of an
6580
* element, and not merely by counting.
@@ -110,4 +125,6 @@ public interface ISelect {
110125
* @param text The visible text to match against
111126
*/
112127
void deselectByVisibleText(String text);
128+
129+
void deSelectByContainsVisibleText(String text);
113130
}

‎java/src/org/openqa/selenium/support/ui/Select.java

Lines changed: 113 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717

1818
package org.openqa.selenium.support.ui;
1919

20-
import java.util.List;
21-
import java.util.Objects;
22-
import java.util.StringTokenizer;
20+
import java.util.*;
2321
import java.util.stream.Collectors;
2422
import org.openqa.selenium.By;
2523
import org.openqa.selenium.NoSuchElementException;
@@ -68,6 +66,22 @@ public boolean isMultiple() {
6866
return isMulti;
6967
}
7068

69+
/**
70+
* @return This is done by checking the value of attributes in "visibility", "display", "opacity"
71+
* Return false if visibility is set to 'hidden', display is 'none', or opacity is 0 or 0.0.
72+
*/
73+
private boolean hasCssPropertyAndVisible(WebElement webElement) {
74+
List<String> cssValueCandidates = Arrays.asList(new String[] {"hidden", "none", "0", "0.0"});
75+
String[] cssPropertyCandidates = new String[] {"visibility", "display", "opacity"};
76+
77+
for (String property : cssPropertyCandidates) {
78+
String cssValue = webElement.getCssValue(property);
79+
if (cssValueCandidates.contains(cssValue)) return false;
80+
}
81+
82+
return true;
83+
}
84+
7185
/**
7286
* @return All options belonging to this select tag
7387
*/
@@ -154,6 +168,75 @@ public void selectByVisibleText(String text) {
154168
}
155169
}
156170

171+
/**
172+
* Selects all options that display text matching or containing the provided argument. This method
173+
* first attempts to find an exact match and, if not found, will then attempt to find options that
174+
* contain the specified text as a substring.
175+
*
176+
* <p>For example, when given "Bar", this would select an option like:
177+
*
178+
* <p>&lt;option value="foo"&gt;Bar&lt;/option&gt;
179+
*
180+
* <p>And also select an option like:
181+
*
182+
* <p>&lt;option value="baz"&gt;FooBar&lt;/option&gt; or &lt;option
183+
* value="baz"&gt;1년납&lt;/option&gt; when "1년" is provided.
184+
*
185+
* @param text The visible text to match against. It can be a full or partial match of the option
186+
* text.
187+
* @throws NoSuchElementException If no matching option elements are found
188+
*/
189+
@Override
190+
public void selectByContainsVisibleText(String text) {
191+
assertSelectIsEnabled();
192+
assertSelectIsVisible();
193+
194+
// try to find the option via XPATH ...
195+
List<WebElement> options =
196+
element.findElements(
197+
By.xpath(".//option[normalize-space(.) = " + Quotes.escape(text) + "]"));
198+
199+
for (WebElement option : options) {
200+
if (!hasCssPropertyAndVisible(option))
201+
throw new NoSuchElementException("Invisible option with text: " + text);
202+
setSelected(option, true);
203+
if (!isMultiple()) {
204+
return;
205+
}
206+
}
207+
208+
boolean matched = !options.isEmpty();
209+
if (!matched) {
210+
String searchText = text.contains(" ") ? getLongestSubstringWithoutSpace(text) : text;
211+
212+
List<WebElement> candidates;
213+
if (searchText.isEmpty()) {
214+
candidates = element.findElements(By.tagName("option"));
215+
} else {
216+
candidates =
217+
element.findElements(
218+
By.xpath(".//option[contains(., " + Quotes.escape(searchText) + ")]"));
219+
}
220+
221+
String trimmed = text.trim();
222+
for (WebElement option : candidates) {
223+
if (option.getText().contains(trimmed)) {
224+
if (!hasCssPropertyAndVisible(option))
225+
throw new NoSuchElementException("Invisible option with text: " + text);
226+
setSelected(option, true);
227+
if (!isMultiple()) {
228+
return;
229+
}
230+
matched = true;
231+
}
232+
}
233+
}
234+
235+
if (!matched) {
236+
throw new NoSuchElementException("Cannot locate option with text: " + text);
237+
}
238+
}
239+
157240
private String getLongestSubstringWithoutSpace(String s) {
158241
String result = "";
159242
StringTokenizer st = new StringTokenizer(s, " ");
@@ -282,6 +365,27 @@ public void deselectByVisibleText(String text) {
282365
}
283366
}
284367

368+
@Override
369+
public void deSelectByContainsVisibleText(String text) {
370+
if (!isMultiple()) {
371+
throw new UnsupportedOperationException("You may only deselect options of a multi-select");
372+
}
373+
374+
String trimmed = text.trim();
375+
List<WebElement> options =
376+
element.findElements(By.xpath(".//option[contains(., " + Quotes.escape(trimmed) + ")]"));
377+
378+
if (options.isEmpty()) {
379+
throw new NoSuchElementException("Cannot locate option with text: " + text);
380+
}
381+
382+
for (WebElement option : options) {
383+
if (!hasCssPropertyAndVisible(option))
384+
throw new NoSuchElementException("Invisible option with text: " + text);
385+
setSelected(option, false);
386+
}
387+
}
388+
285389
private List<WebElement> findOptionsByValue(String value) {
286390
List<WebElement> options =
287391
element.findElements(By.xpath(".//option[@value = " + Quotes.escape(value) + "]"));
@@ -328,6 +432,12 @@ private void assertSelectIsEnabled() {
328432
}
329433
}
330434

435+
private void assertSelectIsVisible() {
436+
if (!hasCssPropertyAndVisible(element)) {
437+
throw new UnsupportedOperationException("You may not select an option in invisible select");
438+
}
439+
}
440+
331441
@Override
332442
public boolean equals(Object o) {
333443
if (!(o instanceof Select)) {

‎java/test/org/openqa/selenium/support/ui/SelectElementTest.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,23 @@ void shouldAllowOptionsToBeSelectedByVisibleText() {
135135
assertThat(firstSelected.getText()).isEqualTo("select_2");
136136
}
137137

138+
@Test
139+
void shouldAllowOptionsToBeSelectedByContainsVisibleText() {
140+
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
141+
142+
Select select = new Select(selectElement);
143+
select.selectByContainsVisibleText("select");
144+
WebElement firstSelected = select.getFirstSelectedOption();
145+
int selectedOptionCount = select.getAllSelectedOptions().size();
146+
147+
assertThat(firstSelected.getText()).isEqualTo("select_1");
148+
assertThat(selectedOptionCount).isEqualTo(4);
149+
150+
select.deselectAll();
151+
assertThatExceptionOfType(NoSuchElementException.class)
152+
.isThrownBy(() -> select.selectByContainsVisibleText("select_12"));
153+
}
154+
138155
@Test
139156
@Ignore(ALL)
140157
public void shouldNotAllowInvisibleOptionsToBeSelectedByVisibleText() {
@@ -145,6 +162,24 @@ public void shouldNotAllowInvisibleOptionsToBeSelectedByVisibleText() {
145162
.isThrownBy(() -> select.selectByVisibleText("Apples"));
146163
}
147164

165+
@Test
166+
void shouldNotAllowInvisibleSelectElementToBeSelectedByContainsVisibleText() {
167+
WebElement selectElement = driver.findElement(By.id("invisi_select"));
168+
Select select = new Select(selectElement);
169+
170+
assertThatExceptionOfType(UnsupportedOperationException.class)
171+
.isThrownBy(() -> select.selectByContainsVisibleText("Apples"));
172+
}
173+
174+
@Test
175+
void shouldNotAllowInvisibleOptionsToBeSelectedByContainsVisibleText() {
176+
WebElement selectElement = driver.findElement(By.id("invisible_multi_select"));
177+
Select select = new Select(selectElement);
178+
179+
assertThatExceptionOfType(NoSuchElementException.class)
180+
.isThrownBy(() -> select.selectByContainsVisibleText("Apples"));
181+
}
182+
148183
@Test
149184
void shouldThrowExceptionOnSelectByVisibleTextIfOptionDoesNotExist() {
150185
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
@@ -243,6 +278,15 @@ void shouldAllowUserToDeselectOptionsByVisibleText() {
243278
assertThat(select.getAllSelectedOptions()).hasSize(1);
244279
}
245280

281+
@Test
282+
void shouldAllowUserToDeselectOptionsByContainsVisibleText() {
283+
WebElement selectElement = driver.findElement(By.name("multi"));
284+
Select select = new Select(selectElement);
285+
select.deSelectByContainsVisibleText("Egg");
286+
287+
assertThat(select.getAllSelectedOptions()).hasSize(1);
288+
}
289+
246290
@Test
247291
@Ignore(ALL)
248292
public void shouldNotAllowUserToDeselectOptionsByInvisibleText() {
@@ -253,6 +297,24 @@ public void shouldNotAllowUserToDeselectOptionsByInvisibleText() {
253297
.isThrownBy(() -> select.deselectByVisibleText("Apples"));
254298
}
255299

300+
@Test
301+
void shouldNotAllowUserDeselectOptionsByContainsText() {
302+
WebElement selectElement = driver.findElement(By.name("multi"));
303+
Select select = new Select(selectElement);
304+
305+
assertThatExceptionOfType(NoSuchElementException.class)
306+
.isThrownBy(() -> select.deSelectByContainsVisibleText("Eggs_"));
307+
}
308+
309+
@Test
310+
void shouldNotAllowUserDeselectOptionsByContainsInvisibleText() {
311+
WebElement selectElement = driver.findElement(By.id("invisible_multi_select"));
312+
Select select = new Select(selectElement);
313+
314+
assertThatExceptionOfType(NoSuchElementException.class)
315+
.isThrownBy(() -> select.deSelectByContainsVisibleText("Apples"));
316+
}
317+
256318
@Test
257319
void shouldAllowOptionsToBeDeselectedByIndex() {
258320
WebElement selectElement = driver.findElement(By.name("multi"));
@@ -299,6 +361,15 @@ void shouldThrowExceptionOnDeselectByVisibleTextIfOptionDoesNotExist() {
299361
.isThrownBy(() -> select.deselectByVisibleText("not there"));
300362
}
301363

364+
@Test
365+
void shouldThrowExceptionOnDeselectByContainsTextIfOptionDoesNotExist() {
366+
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
367+
Select select = new Select(selectElement);
368+
369+
assertThatExceptionOfType(NoSuchElementException.class)
370+
.isThrownBy(() -> select.deSelectByContainsVisibleText("not there"));
371+
}
372+
302373
@Test
303374
void shouldThrowExceptionOnDeselectByIndexIfOptionDoesNotExist() {
304375
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
@@ -334,4 +405,13 @@ void shouldNotAllowUserToDeselectByVisibleTextWhenSelectDoesNotSupportMultipleSe
334405
assertThatExceptionOfType(UnsupportedOperationException.class)
335406
.isThrownBy(() -> select.deselectByVisibleText("Four"));
336407
}
408+
409+
@Test
410+
void shouldNotAllowUserToDeselectByContainsTextDoesNotSupportMultipleSelections() {
411+
WebElement selectElement = driver.findElement(By.name("selectomatic"));
412+
Select select = new Select(selectElement);
413+
414+
assertThatExceptionOfType(UnsupportedOperationException.class)
415+
.isThrownBy(() -> select.deSelectByContainsVisibleText("Four"));
416+
}
337417
}

‎java/test/org/openqa/selenium/support/ui/SelectTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,25 @@ void shouldAllowOptionsToBeSelectedByVisibleText() {
154154
verify(firstOption).click();
155155
}
156156

157+
@Test
158+
void shouldAllowOptionsToBeSelectedByContainsVisibleText() {
159+
String parameterText = "foo";
160+
161+
final WebElement firstOption = mockOption("first", false);
162+
163+
final WebElement element = mockSelectWebElement("multiple");
164+
when(element.findElements(
165+
By.xpath(".//option[contains(., " + Quotes.escape(parameterText) + ")]")))
166+
.thenReturn(Collections.singletonList(firstOption));
167+
when(firstOption.getText()).thenReturn("foo bar");
168+
when(firstOption.isEnabled()).thenReturn(true);
169+
170+
Select select = new Select(element);
171+
select.selectByContainsVisibleText(parameterText);
172+
173+
verify(firstOption).click();
174+
}
175+
157176
@Test
158177
void shouldNotAllowDisabledOptionsToBeSelected() {
159178
final WebElement firstOption = mockOption("first", false);
@@ -231,6 +250,24 @@ void shouldAllowUserToDeselectOptionsByVisibleText() {
231250
verify(secondOption, never()).click();
232251
}
233252

253+
@Test
254+
void shouldAllowOptionsToDeSelectedByContainsVisibleText() {
255+
String parameterText = "b";
256+
final WebElement firstOption = mockOption("first", true);
257+
final WebElement secondOption = mockOption("second", false);
258+
259+
final WebElement element = mockSelectWebElement("multiple");
260+
when(element.findElements(
261+
By.xpath(".//option[contains(., " + Quotes.escape(parameterText) + ")]")))
262+
.thenReturn(Arrays.asList(firstOption, secondOption));
263+
264+
Select select = new Select(element);
265+
select.deSelectByContainsVisibleText(parameterText);
266+
267+
verify(firstOption).click();
268+
verify(secondOption, never()).click();
269+
}
270+
234271
@Test
235272
void shouldAllowOptionsToBeDeselectedByIndex() {
236273
final WebElement firstOption = mockOption("first", true, 2);
@@ -302,5 +339,8 @@ void shouldThrowAnExceptionIfThereAreNoElementsToSelect() {
302339

303340
assertThatExceptionOfType(NoSuchElementException.class)
304341
.isThrownBy(() -> select.selectByVisibleText("also not there"));
342+
343+
assertThatExceptionOfType(NoSuchElementException.class)
344+
.isThrownBy(() -> select.selectByContainsVisibleText("also not there"));
305345
}
306346
}

0 commit comments

Comments
 (0)