diff --git a/common/src/web/formPage.html b/common/src/web/formPage.html index 748e61bcc9d8a..70a31da55cae6 100644 --- a/common/src/web/formPage.html +++ b/common/src/web/formPage.html @@ -80,6 +80,12 @@ Oranges + + Apples + Oranges + Lemons + + One Two diff --git a/java/src/org/openqa/selenium/support/ui/ISelect.java b/java/src/org/openqa/selenium/support/ui/ISelect.java index 510aa5a571ce1..80b233b932bb1 100644 --- a/java/src/org/openqa/selenium/support/ui/ISelect.java +++ b/java/src/org/openqa/selenium/support/ui/ISelect.java @@ -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: + * + * <option value="foo">Bar</option> + * + * 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: + * + * <option value="bar">1년납</option> + * + * @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. @@ -110,4 +125,6 @@ public interface ISelect { * @param text The visible text to match against */ void deselectByVisibleText(String text); + + void deSelectByContainsVisibleText(String text); } diff --git a/java/src/org/openqa/selenium/support/ui/Select.java b/java/src/org/openqa/selenium/support/ui/Select.java index e79a7d39ce0ee..5f4021b8b172d 100644 --- a/java/src/org/openqa/selenium/support/ui/Select.java +++ b/java/src/org/openqa/selenium/support/ui/Select.java @@ -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; @@ -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 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 */ @@ -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. + * + * For example, when given "Bar", this would select an option like: + * + * <option value="foo">Bar</option> + * + * And also select an option like: + * + * <option value="baz">FooBar</option> or <option + * value="baz">1년납</option> 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 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 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, " "); @@ -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 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 findOptionsByValue(String value) { List options = element.findElements(By.xpath(".//option[@value = " + Quotes.escape(value) + "]")); @@ -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)) { diff --git a/java/test/org/openqa/selenium/support/ui/SelectElementTest.java b/java/test/org/openqa/selenium/support/ui/SelectElementTest.java index c01fbdf792cee..0587b630b3261 100644 --- a/java/test/org/openqa/selenium/support/ui/SelectElementTest.java +++ b/java/test/org/openqa/selenium/support/ui/SelectElementTest.java @@ -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() { @@ -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")); @@ -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() { @@ -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")); @@ -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")); @@ -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")); + } } diff --git a/java/test/org/openqa/selenium/support/ui/SelectTest.java b/java/test/org/openqa/selenium/support/ui/SelectTest.java index 64588cac5bb3c..2dd4d1ad48bd6 100644 --- a/java/test/org/openqa/selenium/support/ui/SelectTest.java +++ b/java/test/org/openqa/selenium/support/ui/SelectTest.java @@ -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); @@ -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); @@ -302,5 +339,8 @@ void shouldThrowAnExceptionIfThereAreNoElementsToSelect() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> select.selectByVisibleText("also not there")); + + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> select.selectByContainsVisibleText("also not there")); } }
<option value="foo">Bar</option> + * + *
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: + * + *
<option value="bar">1년납</option> + * + * @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. @@ -110,4 +125,6 @@ public interface ISelect { * @param text The visible text to match against */ void deselectByVisibleText(String text); + + void deSelectByContainsVisibleText(String text); } diff --git a/java/src/org/openqa/selenium/support/ui/Select.java b/java/src/org/openqa/selenium/support/ui/Select.java index e79a7d39ce0ee..5f4021b8b172d 100644 --- a/java/src/org/openqa/selenium/support/ui/Select.java +++ b/java/src/org/openqa/selenium/support/ui/Select.java @@ -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; @@ -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 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 */ @@ -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. + * + * For example, when given "Bar", this would select an option like: + * + * <option value="foo">Bar</option> + * + * And also select an option like: + * + * <option value="baz">FooBar</option> or <option + * value="baz">1년납</option> 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 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 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, " "); @@ -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 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 findOptionsByValue(String value) { List options = element.findElements(By.xpath(".//option[@value = " + Quotes.escape(value) + "]")); @@ -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)) { diff --git a/java/test/org/openqa/selenium/support/ui/SelectElementTest.java b/java/test/org/openqa/selenium/support/ui/SelectElementTest.java index c01fbdf792cee..0587b630b3261 100644 --- a/java/test/org/openqa/selenium/support/ui/SelectElementTest.java +++ b/java/test/org/openqa/selenium/support/ui/SelectElementTest.java @@ -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() { @@ -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")); @@ -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() { @@ -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")); @@ -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")); @@ -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")); + } } diff --git a/java/test/org/openqa/selenium/support/ui/SelectTest.java b/java/test/org/openqa/selenium/support/ui/SelectTest.java index 64588cac5bb3c..2dd4d1ad48bd6 100644 --- a/java/test/org/openqa/selenium/support/ui/SelectTest.java +++ b/java/test/org/openqa/selenium/support/ui/SelectTest.java @@ -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); @@ -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); @@ -302,5 +339,8 @@ void shouldThrowAnExceptionIfThereAreNoElementsToSelect() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> select.selectByVisibleText("also not there")); + + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> select.selectByContainsVisibleText("also not there")); } }
For example, when given "Bar", this would select an option like: + * + *
And also select an option like: + * + *
<option value="baz">FooBar</option> or <option + * value="baz">1년납</option> 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 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 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, " "); @@ -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 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 findOptionsByValue(String value) { List options = element.findElements(By.xpath(".//option[@value = " + Quotes.escape(value) + "]")); @@ -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)) { diff --git a/java/test/org/openqa/selenium/support/ui/SelectElementTest.java b/java/test/org/openqa/selenium/support/ui/SelectElementTest.java index c01fbdf792cee..0587b630b3261 100644 --- a/java/test/org/openqa/selenium/support/ui/SelectElementTest.java +++ b/java/test/org/openqa/selenium/support/ui/SelectElementTest.java @@ -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() { @@ -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")); @@ -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() { @@ -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")); @@ -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")); @@ -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")); + } } diff --git a/java/test/org/openqa/selenium/support/ui/SelectTest.java b/java/test/org/openqa/selenium/support/ui/SelectTest.java index 64588cac5bb3c..2dd4d1ad48bd6 100644 --- a/java/test/org/openqa/selenium/support/ui/SelectTest.java +++ b/java/test/org/openqa/selenium/support/ui/SelectTest.java @@ -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); @@ -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); @@ -302,5 +339,8 @@ void shouldThrowAnExceptionIfThereAreNoElementsToSelect() { assertThatExceptionOfType(NoSuchElementException.class) .isThrownBy(() -> select.selectByVisibleText("also not there")); + + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> select.selectByContainsVisibleText("also not there")); } }