diff --git a/java/client/src/org/openqa/selenium/support/locators/RelativeLocator.java b/java/client/src/org/openqa/selenium/support/locators/RelativeLocator.java index 88e41d37da25d..2af069e4f0e68 100644 --- a/java/client/src/org/openqa/selenium/support/locators/RelativeLocator.java +++ b/java/client/src/org/openqa/selenium/support/locators/RelativeLocator.java @@ -72,6 +72,7 @@ public class RelativeLocator { private static final Json JSON = new Json(); private static final String FIND_ELEMENTS; + static { try { String location = String.format( @@ -87,14 +88,31 @@ public class RelativeLocator { throw new UncheckedIOException(e); } } + private static final int CLOSE_IN_PIXELS = 100; /** * Start of a relative locator, finding elements by tag name. */ - public static RelativeBy withTagName(String tagName) { + + public static RelativeBy with(By by) { + Require.nonNull("By to look for", by); + return new RelativeBy(by); + } + + public static By tagName(String tagName) { Require.nonNull("Tag name to look for", tagName); - return new RelativeBy(By.tagName(tagName)); + return By.tagName(tagName); + } + + public static By xpath(String xpathExpression) { + Require.nonNull("xpath to look for", xpathExpression); + return By.xpath(xpathExpression); + } + + public static By cssSelector(String cssSelectorExpression) { + Require.nonNull("css selector to look for", cssSelectorExpression); + return By.cssSelector(cssSelectorExpression); } public static class RelativeBy extends By implements By.Remotable { diff --git a/java/client/test/org/openqa/selenium/support/locators/RelativeLocatorTest.java b/java/client/test/org/openqa/selenium/support/locators/RelativeLocatorTest.java index c43eb9e7f4fd2..95f1db0470ce1 100644 --- a/java/client/test/org/openqa/selenium/support/locators/RelativeLocatorTest.java +++ b/java/client/test/org/openqa/selenium/support/locators/RelativeLocatorTest.java @@ -28,36 +28,130 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.openqa.selenium.support.locators.RelativeLocator.withTagName; +import static org.openqa.selenium.support.locators.RelativeLocator.withXpath; +import static org.openqa.selenium.support.locators.RelativeLocator.withCssSelector; + public class RelativeLocatorTest extends JUnit4TestBase { @Test - public void shouldBeAbleToFindElementsAboveAnother() { + public void shouldBeAbleToFindElementsAboveAnotherWithTagName() { driver.get(appServer.whereIs("relative_locators.html")); WebElement lowest = driver.findElement(By.id("below")); - List elements = driver.findElements(withTagName("p").above(lowest)); + List elements = driver.findElements(with(tagName("p")).above(lowest)); List ids = elements.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); assertThat(ids).containsExactly("mid", "above"); + + } + + @Test + public void shouldBeAbleToFindElementsAboveAnotherWithXpath() { + driver.get(appServer.whereIs("relative_locators.html")); + + WebElement lowest = driver.findElement(By.id("seventh")); + + List seen = driver.findElements(with(xpath("//td[1]")).above(lowest)); + + List ids = seen.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + + assertThat(ids).containsExactly("fourth", "first"); + } + + @Test + public void shouldBeAbleToFindElementsAboveAnotherwithCssSelector() { + driver.get(appServer.whereIs("relative_locators.html")); + + WebElement lowest = driver.findElement(By.id("below")); + + List elements = driver.findElements(with(cssSelector("p")).above(lowest)); + List ids = elements.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + + assertThat(ids).containsExactly("mid", "above"); + } @Test public void shouldBeAbleToCombineFilters() { driver.get(appServer.whereIs("relative_locators.html")); - List seen = driver.findElements(withTagName("td").above(By.id("center")).toRightOf(By.id("second"))); + List seen = driver.findElements(with(tagName("td")).above(By.id("center")).toRightOf(By.id("second"))); List ids = seen.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + assertThat(ids).containsExactly("third"); } @Test - public void exerciseNearLocator() { + public void shouldBeAbleToCombineFiltersWithXpath() { + driver.get(appServer.whereIs("relative_locators.html")); + + List seen = driver.findElements(with(xpath("//td[1]")).below(By.id("second")).above(By.id("seventh"))); + + List ids = seen.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + + assertThat(ids).containsExactly("fourth"); + + } + + @Test + public void shouldBeAbleToCombineFiltersWithCssSelector() { + driver.get(appServer.whereIs("relative_locators.html")); + + + List seen = driver.findElements(with(cssSelector("td")).above(By.id("center")).toRightOf(By.id("second"))); + + List ids = seen.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + + assertThat(ids).containsExactly("third"); + } + + @Test + public void exerciseNearLocatorWithTagName() { + driver.get(appServer.whereIs("relative_locators.html")); + + List seen = driver.findElements(with(tagName("td")).near(By.id("center"))); + + // Elements are sorted by proximity and then DOM insertion order. + // Proximity is determined using distance from center points, so + // we expect the order to be: + // 1. Directly above (short vertical distance, first in DOM) + // 2. Directly below (short vertical distance, later in DOM) + // 3. Directly left (slight longer distance horizontally, first in DOM) + // 4. Directly right (slight longer distance horizontally, later in DOM) + // 5-8. Diagonally close (pythagorus sorting, with top row first + // because of DOM insertion order) + List ids = seen.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + assertThat(ids).containsExactly("second", "eighth", "fourth", "sixth", "first", "third", "seventh", "ninth"); + } + + @Test + public void exerciseNearLocatorWithXpath() { + driver.get(appServer.whereIs("relative_locators.html")); + + List seen = driver.findElements(with(xpath("//td")).near(By.id("center"))); + + // Elements are sorted by proximity and then DOM insertion order. + // Proximity is determined using distance from center points, so + // we expect the order to be: + // 1. Directly above (short vertical distance, first in DOM) + // 2. Directly below (short vertical distance, later in DOM) + // 3. Directly left (slight longer distance horizontally, first in DOM) + // 4. Directly right (slight longer distance horizontally, later in DOM) + // 5-8. Diagonally close (pythagorus sorting, with top row first + // because of DOM insertion order) + List ids = seen.stream().map(e -> e.getAttribute("id")).collect(Collectors.toList()); + + assertThat(ids).containsExactly("second", "eighth", "fourth", "sixth", "first", "third", "seventh", "ninth"); + } + + @Test + public void exerciseNearLocatorWithCssSelector() { driver.get(appServer.whereIs("relative_locators.html")); - List seen = driver.findElements(withTagName("td").near(By.id("center"))); + List seen = driver.findElements(with(cssSelector("td")).near(By.id("center"))); // Elements are sorted by proximity and then DOM insertion order. // Proximity is determined using distance from center points, so