There are 5 actions in autocomplete,
The input field for us to enter criteria is named "q", so you can use By.name("q") to find it and type the characters of Oracle one by one into it and wait for "Oracle" appears in the selection area and click it.
It is difficult to locate the suggestion items since there is no obvious selection criteria to find them.
You can use Firebug to find its absolute xpath, to get some idea but it can be used in the tests since the structure of the page may change and it will not work,
/html/body/table/tbody/tr/td[2]/table/tbody/tr/td/div/table/tbody/tr/td/span
If you are not familiar with xpath, you can use complex search to find a container first and from the container, find the element. This autocomplete suggestion is inside a table with class "gssb_c", so we can findElement of the table first, and the findElements of all the span elements inside that table and find the one with the text "oracle" you try to type in,
// ☹ this is a bad example, please don't follow the style. try { WebElement table = webDriver.findElement(By.className("gssb_c")); List<WebElement> spans = table.findElements(By.tagName("span")); WebElement oracle = find(spans, new Predicate<WebElement>() { @Override public boolean apply(WebElement span) { return span.getText().equals("oracle"); } }); oracle.click(); } catch (NoSuchElementException e) { e.printStackTrace(); }
This is not the complete logic, it is just the part to find the "oracle" from the suggestion list, it is already very verbose.
Selenium capsules, a new Selenium framework, uses a locator pattern to abstract the element locating logic to make the test code cleaner, in the autoCompleteUsingLocator test from following code block.
Note, () -> className("gssb_c") is a lambda expression introduced in Java 8.
// ☺ This is a good example. new ElementTryLocator<AbstractPage>(() -> className("gssb_c")) .and(new ElementsLocator<>(SPAN)) .and(new FirstMatch<>(TEXT.and(new IsStringEqual("oracle")))));
It is a little bit cleaner, but still very complex.
After rewriting the search using By xpath, it only has one findElement method call, which is much better than the navigational locating strategy, so you can greatly simplify the test if you know how to use xpath.
// ☹ this is a bad example, please don't follow the style. try { WebElement oracle = webDriver.findElement( By.xpath("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']"); oracle.click(); } catch (NoSuchElementException e) { e.printStackTrace(); }
But it is still not clean if the By.xpath call appears multiple times in different tests,
By.xpath("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']");
By using Selenium Capsules framework, you can actually put this xpath string literal inside an enum and use the enum in the tests,
// ☺ This is a good example. import org.openqa.selenium.By; import java.util.function.Supplier; import static org.openqa.selenium.By.xpath; public enum Xpath implements Supplier<By> { DIV_CONTAINER_ID("//div[@id='container']"), ORACLE_AUTOCOMPLETE("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']"), QUANTITY("//div[@id='ys_cartInfo']/descendant::input[@name='cartDS.shoppingcart_ROW0_m_orderItemVector_ROW0_m_quantity']"); private final By by; private Xpath(String id) { this.by = xpath(id); } @Override public By get() { return by; } @Override public String toString() { return by.toString(); } }
Now the test can be rewritten as simple as one line of code,
// ☺ This is a good example. @Test public void autoCompleteUsingXpath() { googlePage.autocomplete(Q, "oracle", new ElementTryLocator<>(ORACLE_AUTOCOMPLETE)); }
Even you don't want to use a framework, putting locating strategy in enum can still simplify code, unlike the above code, you can just use ORACLE_AUTOCOMPLETE as a parameter, you need to call get() method to get a By object, but it is still cleaner than using xpath string literals directly in the code,
// ☺ This is an OK example. try { WebElement oracle = webDriver.findElement(ORACLE_AUTOCOMPLETE.get()); oracle.click(); } catch (NoSuchElementException e) { e.printStackTrace(); }
versus original,
// ☹ this is a bad example, please don't follow the style. try { WebElement oracle = webDriver.findElement( By.xpath("//table[contains(concat(' ', @class, ' '), 'gssb_c')]/descendant::span[text()='oracle']"); oracle.click(); } catch (NoSuchElementException e) { e.printStackTrace(); }
Now let us have a comparison between the tests written with and without using framework,
1. autoCompeleteUsingSelenium doesn't use framework, it uses Selenium directly.
2. autoCompleteUsingXpath uses framework,
Cohesively, all By selectors can be organized into one package, selectors to encourage people reuse existing definitions, the name of that package should be bysuppliers, I found out selectors is a more meaningful name since that's the purpose of those classes.
By Selector Suppliers and all its member classes are enum types implementing Supplier