Skip to content

Commit

Permalink
[java] feat: Add method to select options containing the provided text (
Browse files Browse the repository at this point in the history
#14426)


---------

Signed-off-by: Viet Nguyen Duc <[email protected]>
Co-authored-by: syber911911 <[email protected]>
Co-authored-by: Viet Nguyen Duc <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2024
1 parent 19a4546 commit b4b8aab
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 3 deletions.
6 changes: 6 additions & 0 deletions common/src/web/formPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
<option value="oranges">Oranges</option>
</select>

<select id="invisible_multi_select" multiple>
<option selected="selected" value="apples" style="opacity: 0;">Apples</option>
<option value="oranges">Oranges</option>
<option selected="selected" value="lemons">Lemons</option>
</select>

<select name="select-default">
<option>One</option>
<option>Two</option>
Expand Down
17 changes: 17 additions & 0 deletions java/src/org/openqa/selenium/support/ui/ISelect.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,21 @@ public interface ISelect {
*/
void selectByVisibleText(String text);

/**
* Select all options that display text matching or containing the argument. That is, when given
* "Bar" this would select an option like:
*
* <p>&lt;option value="foo"&gt;Bar&lt;/option&gt;
*
* <p>Additionally, if no exact match is found, this will attempt to select options that contain
* the argument as a substring. For example, when given "1년", this would select an option like:
*
* <p>&lt;option value="bar"&gt;1년납&lt;/option&gt;
*
* @param text The visible text to match or partially match against
*/
void selectByContainsVisibleText(String text);

/**
* Select the option at the given index. This is done by examining the "index" attribute of an
* element, and not merely by counting.
Expand Down Expand Up @@ -110,4 +125,6 @@ public interface ISelect {
* @param text The visible text to match against
*/
void deselectByVisibleText(String text);

void deSelectByContainsVisibleText(String text);
}
116 changes: 113 additions & 3 deletions java/src/org/openqa/selenium/support/ui/Select.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@

package org.openqa.selenium.support.ui;

import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
import java.util.*;
import java.util.stream.Collectors;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
Expand Down Expand Up @@ -68,6 +66,22 @@ public boolean isMultiple() {
return isMulti;
}

/**
* @return This is done by checking the value of attributes in "visibility", "display", "opacity"
* Return false if visibility is set to 'hidden', display is 'none', or opacity is 0 or 0.0.
*/
private boolean hasCssPropertyAndVisible(WebElement webElement) {
List<String> cssValueCandidates = Arrays.asList(new String[] {"hidden", "none", "0", "0.0"});
String[] cssPropertyCandidates = new String[] {"visibility", "display", "opacity"};

for (String property : cssPropertyCandidates) {
String cssValue = webElement.getCssValue(property);
if (cssValueCandidates.contains(cssValue)) return false;
}

return true;
}

/**
* @return All options belonging to this select tag
*/
Expand Down Expand Up @@ -154,6 +168,75 @@ public void selectByVisibleText(String text) {
}
}

/**
* Selects all options that display text matching or containing the provided argument. This method
* first attempts to find an exact match and, if not found, will then attempt to find options that
* contain the specified text as a substring.
*
* <p>For example, when given "Bar", this would select an option like:
*
* <p>&lt;option value="foo"&gt;Bar&lt;/option&gt;
*
* <p>And also select an option like:
*
* <p>&lt;option value="baz"&gt;FooBar&lt;/option&gt; or &lt;option
* value="baz"&gt;1년납&lt;/option&gt; when "1년" is provided.
*
* @param text The visible text to match against. It can be a full or partial match of the option
* text.
* @throws NoSuchElementException If no matching option elements are found
*/
@Override
public void selectByContainsVisibleText(String text) {
assertSelectIsEnabled();
assertSelectIsVisible();

// try to find the option via XPATH ...
List<WebElement> options =
element.findElements(
By.xpath(".//option[normalize-space(.) = " + Quotes.escape(text) + "]"));

for (WebElement option : options) {
if (!hasCssPropertyAndVisible(option))
throw new NoSuchElementException("Invisible option with text: " + text);
setSelected(option, true);
if (!isMultiple()) {
return;
}
}

boolean matched = !options.isEmpty();
if (!matched) {
String searchText = text.contains(" ") ? getLongestSubstringWithoutSpace(text) : text;

List<WebElement> candidates;
if (searchText.isEmpty()) {
candidates = element.findElements(By.tagName("option"));
} else {
candidates =
element.findElements(
By.xpath(".//option[contains(., " + Quotes.escape(searchText) + ")]"));
}

String trimmed = text.trim();
for (WebElement option : candidates) {
if (option.getText().contains(trimmed)) {
if (!hasCssPropertyAndVisible(option))
throw new NoSuchElementException("Invisible option with text: " + text);
setSelected(option, true);
if (!isMultiple()) {
return;
}
matched = true;
}
}
}

if (!matched) {
throw new NoSuchElementException("Cannot locate option with text: " + text);
}
}

private String getLongestSubstringWithoutSpace(String s) {
String result = "";
StringTokenizer st = new StringTokenizer(s, " ");
Expand Down Expand Up @@ -282,6 +365,27 @@ public void deselectByVisibleText(String text) {
}
}

@Override
public void deSelectByContainsVisibleText(String text) {
if (!isMultiple()) {
throw new UnsupportedOperationException("You may only deselect options of a multi-select");
}

String trimmed = text.trim();
List<WebElement> options =
element.findElements(By.xpath(".//option[contains(., " + Quotes.escape(trimmed) + ")]"));

if (options.isEmpty()) {
throw new NoSuchElementException("Cannot locate option with text: " + text);
}

for (WebElement option : options) {
if (!hasCssPropertyAndVisible(option))
throw new NoSuchElementException("Invisible option with text: " + text);
setSelected(option, false);
}
}

private List<WebElement> findOptionsByValue(String value) {
List<WebElement> options =
element.findElements(By.xpath(".//option[@value = " + Quotes.escape(value) + "]"));
Expand Down Expand Up @@ -328,6 +432,12 @@ private void assertSelectIsEnabled() {
}
}

private void assertSelectIsVisible() {
if (!hasCssPropertyAndVisible(element)) {
throw new UnsupportedOperationException("You may not select an option in invisible select");
}
}

@Override
public boolean equals(Object o) {
if (!(o instanceof Select)) {
Expand Down
80 changes: 80 additions & 0 deletions java/test/org/openqa/selenium/support/ui/SelectElementTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ void shouldAllowOptionsToBeSelectedByVisibleText() {
assertThat(firstSelected.getText()).isEqualTo("select_2");
}

@Test
void shouldAllowOptionsToBeSelectedByContainsVisibleText() {
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));

Select select = new Select(selectElement);
select.selectByContainsVisibleText("select");
WebElement firstSelected = select.getFirstSelectedOption();
int selectedOptionCount = select.getAllSelectedOptions().size();

assertThat(firstSelected.getText()).isEqualTo("select_1");
assertThat(selectedOptionCount).isEqualTo(4);

select.deselectAll();
assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.selectByContainsVisibleText("select_12"));
}

@Test
@Ignore(ALL)
public void shouldNotAllowInvisibleOptionsToBeSelectedByVisibleText() {
Expand All @@ -145,6 +162,24 @@ public void shouldNotAllowInvisibleOptionsToBeSelectedByVisibleText() {
.isThrownBy(() -> select.selectByVisibleText("Apples"));
}

@Test
void shouldNotAllowInvisibleSelectElementToBeSelectedByContainsVisibleText() {
WebElement selectElement = driver.findElement(By.id("invisi_select"));
Select select = new Select(selectElement);

assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> select.selectByContainsVisibleText("Apples"));
}

@Test
void shouldNotAllowInvisibleOptionsToBeSelectedByContainsVisibleText() {
WebElement selectElement = driver.findElement(By.id("invisible_multi_select"));
Select select = new Select(selectElement);

assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.selectByContainsVisibleText("Apples"));
}

@Test
void shouldThrowExceptionOnSelectByVisibleTextIfOptionDoesNotExist() {
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
Expand Down Expand Up @@ -243,6 +278,15 @@ void shouldAllowUserToDeselectOptionsByVisibleText() {
assertThat(select.getAllSelectedOptions()).hasSize(1);
}

@Test
void shouldAllowUserToDeselectOptionsByContainsVisibleText() {
WebElement selectElement = driver.findElement(By.name("multi"));
Select select = new Select(selectElement);
select.deSelectByContainsVisibleText("Egg");

assertThat(select.getAllSelectedOptions()).hasSize(1);
}

@Test
@Ignore(ALL)
public void shouldNotAllowUserToDeselectOptionsByInvisibleText() {
Expand All @@ -253,6 +297,24 @@ public void shouldNotAllowUserToDeselectOptionsByInvisibleText() {
.isThrownBy(() -> select.deselectByVisibleText("Apples"));
}

@Test
void shouldNotAllowUserDeselectOptionsByContainsText() {
WebElement selectElement = driver.findElement(By.name("multi"));
Select select = new Select(selectElement);

assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.deSelectByContainsVisibleText("Eggs_"));
}

@Test
void shouldNotAllowUserDeselectOptionsByContainsInvisibleText() {
WebElement selectElement = driver.findElement(By.id("invisible_multi_select"));
Select select = new Select(selectElement);

assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.deSelectByContainsVisibleText("Apples"));
}

@Test
void shouldAllowOptionsToBeDeselectedByIndex() {
WebElement selectElement = driver.findElement(By.name("multi"));
Expand Down Expand Up @@ -299,6 +361,15 @@ void shouldThrowExceptionOnDeselectByVisibleTextIfOptionDoesNotExist() {
.isThrownBy(() -> select.deselectByVisibleText("not there"));
}

@Test
void shouldThrowExceptionOnDeselectByContainsTextIfOptionDoesNotExist() {
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
Select select = new Select(selectElement);

assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.deSelectByContainsVisibleText("not there"));
}

@Test
void shouldThrowExceptionOnDeselectByIndexIfOptionDoesNotExist() {
WebElement selectElement = driver.findElement(By.name("select_empty_multiple"));
Expand Down Expand Up @@ -334,4 +405,13 @@ void shouldNotAllowUserToDeselectByVisibleTextWhenSelectDoesNotSupportMultipleSe
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> select.deselectByVisibleText("Four"));
}

@Test
void shouldNotAllowUserToDeselectByContainsTextDoesNotSupportMultipleSelections() {
WebElement selectElement = driver.findElement(By.name("selectomatic"));
Select select = new Select(selectElement);

assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> select.deSelectByContainsVisibleText("Four"));
}
}
40 changes: 40 additions & 0 deletions java/test/org/openqa/selenium/support/ui/SelectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,25 @@ void shouldAllowOptionsToBeSelectedByVisibleText() {
verify(firstOption).click();
}

@Test
void shouldAllowOptionsToBeSelectedByContainsVisibleText() {
String parameterText = "foo";

final WebElement firstOption = mockOption("first", false);

final WebElement element = mockSelectWebElement("multiple");
when(element.findElements(
By.xpath(".//option[contains(., " + Quotes.escape(parameterText) + ")]")))
.thenReturn(Collections.singletonList(firstOption));
when(firstOption.getText()).thenReturn("foo bar");
when(firstOption.isEnabled()).thenReturn(true);

Select select = new Select(element);
select.selectByContainsVisibleText(parameterText);

verify(firstOption).click();
}

@Test
void shouldNotAllowDisabledOptionsToBeSelected() {
final WebElement firstOption = mockOption("first", false);
Expand Down Expand Up @@ -231,6 +250,24 @@ void shouldAllowUserToDeselectOptionsByVisibleText() {
verify(secondOption, never()).click();
}

@Test
void shouldAllowOptionsToDeSelectedByContainsVisibleText() {
String parameterText = "b";
final WebElement firstOption = mockOption("first", true);
final WebElement secondOption = mockOption("second", false);

final WebElement element = mockSelectWebElement("multiple");
when(element.findElements(
By.xpath(".//option[contains(., " + Quotes.escape(parameterText) + ")]")))
.thenReturn(Arrays.asList(firstOption, secondOption));

Select select = new Select(element);
select.deSelectByContainsVisibleText(parameterText);

verify(firstOption).click();
verify(secondOption, never()).click();
}

@Test
void shouldAllowOptionsToBeDeselectedByIndex() {
final WebElement firstOption = mockOption("first", true, 2);
Expand Down Expand Up @@ -302,5 +339,8 @@ void shouldThrowAnExceptionIfThereAreNoElementsToSelect() {

assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.selectByVisibleText("also not there"));

assertThatExceptionOfType(NoSuchElementException.class)
.isThrownBy(() -> select.selectByContainsVisibleText("also not there"));
}
}

0 comments on commit b4b8aab

Please sign in to comment.