Skip to content

Commit

Permalink
Expose locator generation methods in Java
Browse files Browse the repository at this point in the history
NEW: Correctly handle `context` when it is a WebElement, and test this
PiperOrigin-RevId: 362292973
  • Loading branch information
AlexLloyd0 authored and copybara-github committed Mar 11, 2021
1 parent 659d25a commit 323dbca
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 14 deletions.
2 changes: 2 additions & 0 deletions webdriver_java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.google.semanticlocators.BySemanticLocator;

WebElement searchButton = driver.findElement(new BySemanticLocator("{button 'Google search'}"));
ArrayList<WebElement> allButtons = driver.findElements(new BySemanticLocator("{button}"));

String generated = BySemanticLocator.closestPreciseLocatorFor(searchButton); // {button 'Google search'}
```

General Semantic Locator documentation can be found on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,46 +57,89 @@ public BySemanticLocator(String semanticLocator) {

@Override
public ArrayList<WebElement> findElements(SearchContext context) {
Object result = callJsFunction(context, "findElementsBySemanticLocator", semanticLocator);
Object result =
callJsFunction(
getExecutor(context),
"findElementsBySemanticLocator",
getArgs(semanticLocator, context));
@SuppressWarnings("unchecked")
ArrayList<WebElement> cast = (ArrayList<WebElement>) result;
return cast;
}

@Override
public WebElement findElement(SearchContext context) {
return (WebElement) callJsFunction(context, "findElementBySemanticLocator", semanticLocator);
return (WebElement)
callJsFunction(
getExecutor(context),
"findElementBySemanticLocator",
getArgs(semanticLocator, context));
}

protected static final Object callJsFunction(
SearchContext context, String function, Object argument) {
loadDefinition(context);
private static Object[] getArgs(String semanticLocator, SearchContext context) {
return (context instanceof WebElement
? new Object[] {semanticLocator, context}
: new Object[] {semanticLocator});
}

/**
* Builds the most precise locator which matches `element`. If `element` does not have a role,
* return a semantic locator which matches the closest ancestor with a role. "Precise" means that
* it matches the fewest other elements, while being as short as possible.
*/
public static String closestPreciseLocatorFor(WebElement element) {
return (String) callJsFunction(getExecutor(element), "closestPreciseLocatorFor", element);
}

/**
* Builds the most precise locator which matches `element`, using `rootEl` as the root. If
* `element` does not have a role, return a semantic locator which matches the closest ancestor
* with a role. "Precise" means that it matches the fewest other elements, while being as short as
* possible.
*/
public static String closestPreciseLocatorFor(WebElement element, WebElement rootEl) {
return (String)
callJsFunction(getExecutor(element), "closestPreciseLocatorFor", element, rootEl);
}

/**
* Builds the most precise locator which matches `element`. "Precise" means that it matches the
* fewest other elements, while being as short as possible.
*/
public static String preciseLocatorFor(WebElement element) {
return (String) callJsFunction(getExecutor(element), "preciseLocatorFor", element);
}

/**
* Builds the most precise locator which matches `element`, using `rootEl` as the root. "Precise"
* means that it matches the fewest other elements, while being as short as possible.
*/
public static String preciseLocatorFor(WebElement element, WebElement rootEl) {
return (String) callJsFunction(getExecutor(element), "preciseLocatorFor", element, rootEl);
}

protected static final Object callJsFunction(
JavascriptExecutor executor, String function, Object... args) {
loadDefinition(executor);
String script = "return window." + function + ".apply(null, arguments);";

Object result;
try {
result =
context instanceof WebElement
? getExecutor(context).executeScript(script, argument, context)
: getExecutor(context).executeScript(script, argument);
return executor.executeScript(script, args);
} catch (JavascriptException e) {
throw deserializeException(e);
}
return result;
}

private static void loadDefinition(SearchContext context) {
private static void loadDefinition(JavascriptExecutor executor) {
// TODO(alexlloyd) it might actually be more efficient to load+call semantic locators in one
// script each time. It depends how the round trip of a call to executeScript compares with the
// time to load the definition
JavascriptExecutor executor = getExecutor(context);
if ((Boolean) executor.executeScript("return window.semanticLocatorsReady !== true;")) {
executor.executeScript(JS_IMPLEMENTATION);
}
}

private static JavascriptExecutor getExecutor(SearchContext context) {
protected static JavascriptExecutor getExecutor(SearchContext context) {
if (context instanceof JavascriptExecutor) {
return (JavascriptExecutor) context;
} else if (context instanceof RemoteWebElement) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.junit.AfterClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.By;
import org.openqa.selenium.InvalidSelectorException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
Expand Down Expand Up @@ -73,6 +74,28 @@ private static ImmutableSet<String> getAllDriverNames() {
return DRIVERS.keySet();
}

@Test
@Parameters(method = "getAllDriverNames")
public void findElement_findsWithinContext(String driverName) throws Exception {
WebDriver driver = getDriver(driverName);
renderHtml(
"<button>OK</button><div id='container'><button id='target'>OK</button></div>", driver);
WebElement element =
driver.findElement(By.id("container")).findElement(new BySemanticLocator("{button 'OK'}"));
assertThat(element.getAttribute("id")).isEqualTo("target");
}

@Test
@Parameters(method = "getAllDriverNames")
public void findElements_findsWithinContext(String driverName) throws Exception {
WebDriver driver = getDriver(driverName);
renderHtml(
"<button>OK</button><div id='container'><button id='target'>OK</button></div>", driver);
List<WebElement> elements =
driver.findElement(By.id("container")).findElements(new BySemanticLocator("{button 'OK'}"));
assertThat(elements).hasSize(1);
}

@Test
@Parameters(method = "invalidSyntaxTests")
public void findElements_throwsExceptionForInvalidSyntax(String semantic, String driverName)
Expand All @@ -95,6 +118,95 @@ public void findElements_throwsExceptionForNoElementsFound(String driverName) th
() -> driver.findElement(new BySemanticLocator("{button 'this label does not exist'}")));
}

@Test
@Parameters(method = "preciseLocatorForWithoutRootTests")
public void preciseLocatorFor_generatesLocatorForElement(
String expected, String html, String driverName) {
WebDriver driver = getDriver(driverName);
renderHtml(html, driver);

WebElement target = driver.findElement(By.id("target"));
assertThat(BySemanticLocator.preciseLocatorFor(target)).isEqualTo(expected);
}

private static List<List<String>> preciseLocatorForWithoutRootTests() {
return withAllDriverNames(
asList(
asList("{button 'OK'}", "<button id='target'>OK</button>"),
asList(
"{listitem} {button 'OK'}",
"<ul><li><button id='target'>OK</button></li></ul><button>OK</button>"),
asList(null, "<button><div id='target'>OK</div></button>")));
}

@Test
@Parameters(method = "preciseLocatorForWithRootTests")
public void preciseLocatorFor_acceptsRootEl(String expected, String html, String driverName) {
WebDriver driver = getDriver(driverName);
renderHtml(html, driver);

WebElement target = driver.findElement(By.id("target"));
WebElement root = driver.findElement(By.id("root"));
assertThat(BySemanticLocator.preciseLocatorFor(target, root)).isEqualTo(expected);
}

private static List<List<String>> preciseLocatorForWithRootTests() {
return withAllDriverNames(
asList(
asList("{button 'OK'}", "<div id='root'><button id='target'>OK</button></div>"),
asList(
"{button 'OK'}",
"<div id='root'><ul><li><button id='target'>OK</button></li></ul></div>"
+ "<button>OK</button>"),
asList(null, "<div id='root'><button><div id='target'>OK</div></button></div>")));
}

@Test
@Parameters(method = "closestPreciseLocatorForWithoutRootTests")
public void closestPreciseLocatorFor_generatesLocatorForElement(
String expected, String html, String driverName) {
WebDriver driver = getDriver(driverName);
renderHtml(html, driver);

WebElement target = driver.findElement(By.id("target"));
assertThat(BySemanticLocator.closestPreciseLocatorFor(target)).isEqualTo(expected);
}

private static List<List<String>> closestPreciseLocatorForWithoutRootTests() {
return withAllDriverNames(
asList(
asList("{button 'OK'}", "<button id='target'>OK</button>"),
asList(
"{listitem} {button 'OK'}",
"<ul><li><button id='target'>OK</button></li></ul><button>OK</button>"),
asList("{button 'OK'}", "<button><div id='target'>OK</div></button>")));
}

@Test
@Parameters(method = "closestPreciseLocatorForWithRootTests")
public void closestPreciseLocatorFor_acceptsRootEl(
String expected, String html, String driverName) {
WebDriver driver = getDriver(driverName);
renderHtml(html, driver);

WebElement target = driver.findElement(By.id("target"));
WebElement root = driver.findElement(By.id("root"));
assertThat(BySemanticLocator.closestPreciseLocatorFor(target, root)).isEqualTo(expected);
}

private static List<List<String>> closestPreciseLocatorForWithRootTests() {
return withAllDriverNames(
asList(
asList("{button 'OK'}", "<div id='root'><button id='target'>OK</button></div>"),
asList(
"{button 'OK'}",
"<div id='root'><ul><li><button id='target'>OK</button></li></ul></div>"
+ "<button>OK</button>"),
asList(
"{button 'OK'}",
"<div id='root'><button><div id='target'>OK</div></button></div>")));
}

private static void renderHtml(String html, WebDriver driver) {
String browserName = ((RemoteWebDriver) driver).getCapabilities().getBrowserName();
// IE doesn't support data URLs
Expand Down

0 comments on commit 323dbca

Please sign in to comment.