selenium capsules
advanced tutorials for selenium 2 webdriver and functional programming in java 8
Monday, November 21, 2016
Friday, March 11, 2016
Saturday, June 14, 2014
handle ajax
When testing web sites built with AJAX technology, special cares must be taken to make sure when you call webDriver.findElement. Here is a web site selling event tickets, Ticketfly, to test the change location function, you may be attempted to write a test like this,
Unfortunately, this test doesn't work, you will get this exception when you try to run the test,
The reason is this function is built upon AJAX, it doesn't refresh the entire page, it just repaint the specific area with the new DOM elements. In order to test this feature, we need to add Explicit Wait mechanism to make sure the elements we are looking for appear after certain waiting period, the suitable classes from Selenium are WebDriverWait and FluentWait, using either one, we can rewrite the about test as following,
When you run it, it passes.
This test become very verbose when we try to address some common concerns such as wait, it repeats many similar but not exactly same codes. It takes longer time for other developer to read the code and understand what it is doing. We can use framework approach to clean up the code, let us compare the following test written using Selenium Capsules,
It is much cleaner if you write code using framework approach since framework handles many tedious common concerns such as wait and see.
The test can be further cleaned by introducing a Page Object,
Even I am impressed by my own work. In case you wonder which class handles the Explicit Wait, it is the ElementLocator class in Selenium Capsules, it calls the untilFound method of Searchable interface as described here.
Unlike the original findElement method in SearchContext of the Selenium API, two new methods are introduced to handle different situations,1. If an element may appear or may not, use tryElement, it returns null if not found.
2. If an element will appear after sometime since it is AJAX managed DOM element, use untilFound method which is explicitly waiting for the element to appear.
@Test public void changeLocationUsingSelenium() { System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver"); WebDriver webDriver = new ChromeDriver(); webDriver.get("http://www.ticketfly.com"); webDriver.findElement(linkText("change location")).click(); webDriver.findElement(linkText("CANADA")).click(); webDriver.findElement(linkText("All Canada")).click(); assertEquals("Canada", webDriver.findElement(By.className("tools-location")) .findElement(By.tagName("a")) .findElement(By.tagName("strong")) .getText()); }
Unfortunately, this test doesn't work, you will get this exception when you try to run the test,
Starting ChromeDriver (v2.10.267517) on port 39666 Only local connections are allowed. log4j:WARN No appenders could be found for logger (org.apache.http.client.protocol.RequestAddCookies). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. org.openqa.selenium.NoSuchElementException: no such element (Session info: chrome=35.0.1916.153) (Driver info: chromedriver=2.10.267517,platform=Mac OS X 10.9.3 x86_64) (WARNING: The server did not provide any stacktrace information) Command duration or timeout: 115 milliseconds For documentation on this error, please visit: http://seleniumhq.org/exceptions/no_such_element.html Build info: version: '2.42.2', revision: '6a6995d31c7c56c340d6f45a76976d43506cd6cc', time: '2014-06-03 10:52:47' System info: host: 'yujun.home', ip: '192.168.1.2', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.9.3', java.version: '1.8.0_05' Driver info: org.openqa.selenium.chrome.ChromeDriver Capabilities [{applicationCacheEnabled=false, rotatable=false, chrome={userDataDir=/var/folders/ks/4h1b7nps1vx5712vz12qd3880000gn/T/.org.chromium.Chromium.WmhgSi}, takesHeapSnapshot=true, databaseEnabled=false, handlesAlerts=true, version=35.0.1916.153, platform=MAC, browserConnectionEnabled=false, nativeEvents=true, acceptSslCerts=true, locationContextEnabled=true, webStorageEnabled=true, browserName=chrome, takesScreenshot=true, javascriptEnabled=true, cssSelectorsEnabled=true}] Session ID: e2f0a757e78c351f2808a6b957c534c5 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:408) at org.openqa.selenium.remote.ErrorHandler.createThrowable(ErrorHandler.java:204) at org.openqa.selenium.remote.ErrorHandler.throwIfResponseFailed(ErrorHandler.java:156) at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:599) at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:352) at org.openqa.selenium.remote.RemoteWebDriver.findElementByLinkText(RemoteWebDriver.java:401) at org.openqa.selenium.By$ByLinkText.findElement(By.java:242) at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:344) at com.algocrafts.TicketflyTest.changeLocationUsingSelenium(TicketflyTest.java:26) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134) Process finished with exit code 255
The reason is this function is built upon AJAX, it doesn't refresh the entire page, it just repaint the specific area with the new DOM elements. In order to test this feature, we need to add Explicit Wait mechanism to make sure the elements we are looking for appear after certain waiting period, the suitable classes from Selenium are WebDriverWait and FluentWait, using either one, we can rewrite the about test as following,
@Test public void changeLocationUsingSeleniumWithExplicitWait() { System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver"); WebDriver webDriver = new ChromeDriver(); webDriver.get("http://www.ticketfly.com"); webDriver.findElement(linkText("change location")).click(); WebDriverWait wait = new WebDriverWait(webDriver, 5); WebElement canada = wait.until(new Function<WebDriver, WebElement>() { @Override public WebElement apply(WebDriver webDriver) { return webDriver.findElement(linkText("CANADA")); } }); canada.click(); WebElement allCanada = wait.until(new Function<WebDriver, WebElement>() { @Override public WebElement apply(WebDriver webDriver) { return webDriver.findElement(linkText("All Canada")); } }); allCanada.click(); assertEquals("Canada", webDriver.findElement(By.className("tools-location")) .findElement(By.tagName("a")) .findElement(By.tagName("strong")) .getText()); }
When you run it, it passes.
This test become very verbose when we try to address some common concerns such as wait, it repeats many similar but not exactly same codes. It takes longer time for other developer to read the code and understand what it is doing. We can use framework approach to clean up the code, let us compare the following test written using Selenium Capsules,
@Test public void changeLocationUsingBrowser() { Browser browser = CHROME; browser.get("http://www.ticketfly.com"); browser.link(CHANGE_LOCATION).click(); browser.link(CANADA).click(); browser.link(ALL_CANADA).click(); assertEquals("Canada", Locators.<AbstractPage>element(TOOLS_LOCATION) .and(element(A)) .and(element(STRONG)) .and(TEXT).locate(new Page(browser))); }
It is much cleaner if you write code using framework approach since framework handles many tedious common concerns such as wait and see.
The test can be further cleaned by introducing a Page Object,
@Test public void changeLocation() { TicketflyHomePage page = new TicketflyHomePage(CHROME); page.open(); page.changeLocation(CANADA, ALL_CANADA); assertEquals("Canada", page.currentLocation()); }
Even I am impressed by my own work. In case you wonder which class handles the Explicit Wait, it is the ElementLocator class in Selenium Capsules, it calls the untilFound method of Searchable interface as described here.
public class ElementLocator<Where extends Searchable<Where>> extends Locators<Where, Element> { public ElementLocator(Supplier<By> selector) { super((Where where) -> where.untilFound(selector)); } }
Unlike the original findElement method in SearchContext of the Selenium API, two new methods are introduced to handle different situations,
/** * Find the first element or return null if nothing found. * * @param by selector * @return the first element or return null if nothing found. */ default public Element tryElement(Supplier<By> by) { try { return findElement(by.get()); } catch (NoSuchElementException e) { return null; } } /** * Find the first element until timeout then throw NoSuchElementException * * @param by selector * @return the first element or throw NoSuchElementException */ default public Element untilFound(Supplier<By> by) { return until((Where page) -> findElement(by.get())); }
Thursday, June 12, 2014
handle alert and confirm
The following two tests demonstrate how to click "OK" on a confirm message box, the first method uses Selenium directly, the second one uses Selenium Capsules framework,
public class ConfirmTest { @Test public void clickConfirmUsingSelenium() { System.setProperty("webdriver.chrome.driver", "src/main/resources/chrome/chromedriver"); WebDriver webDriver = new ChromeDriver(); webDriver.get("http://localhost:63342/seleniumcapsules/html/upload.html"); webDriver.findElement(cssSelector("input[value='Need Confirm']")).click(); webDriver.switchTo().alert().accept(); } @Test public void clickConfirm() { Page page = CHROME.load("http://localhost:63342/seleniumcapsules/html/upload.html"); page.button(NEED_CONIRM).click(); page.accept(); } }
Sunday, June 8, 2014
upload file
Some applications may require user to upload the file and since it is part of the workflow, without it, the test can be executed end to end, so it is necessary to add the support for file upload.
Actually file upload is simpler than you think, even it appears like you have to browser the folder to find the file, but it is not a case when you do test automation. The browsing part of the function of the browser, it is out of your control so you don't need to test that part. As tester, we only test the part we are responsible for, for example, after you choose the file, click the button and the file should be sent to server and process by the application. So we can use put the file under test source folder and use Java File to locate it and convert the file into a file and use it for the testing.
We can put the file to be uploaded inside /test/resources/upload/ folder and refer it in the test.
The button doesn't submit the form, it just alert a message instead, so the last part of the test is to accept the alert.
The test can be managed by Spring as well,
Actually file upload is simpler than you think, even it appears like you have to browser the folder to find the file, but it is not a case when you do test automation. The browsing part of the function of the browser, it is out of your control so you don't need to test that part. As tester, we only test the part we are responsible for, for example, after you choose the file, click the button and the file should be sent to server and process by the application. So we can use put the file under test source folder and use Java File to locate it and convert the file into a file and use it for the testing.
We can put the file to be uploaded inside /test/resources/upload/ folder and refer it in the test.
The button doesn't submit the form, it just alert a message instead, so the last part of the test is to accept the alert.
The test can be managed by Spring as well,
locating
I found out one commonality between all form elements,
1. They all have these two instance fields, Where and Locator;
2. They all call locator.locate(where) before taking other actions.
To use one word to describe this activity of the locator, it is "locating"
Thus this class Locating is introduced into Selenium Capsules framework,
And now Input is
Checkbox became,
Radio became,
There no longer have these two instance fields,
protected final Where where;
protected final Locator locator;
Andlocator.locate(where) became locate(). There is change in the function call chain as well, radioButtonGroup used to be the first function in the chain and now it is FirstMatch, after radioButtonGroup became the locator variable in the super class and locator.locate(where) became locate(), to illustrate the functional transformation, the following three function calls have the same effect.
They all are equivalent to this sequential form,
which is exactly same to this raw form, without Selenium Capsules, you can see a lot of Selenium powder.
1. They all have these two instance fields, Where and Locator;
2. They all call locator.locate(where) before taking other actions.
To use one word to describe this activity of the locator, it is "locating"
Thus this class Locating is introduced into Selenium Capsules framework,
public class Locating<Where extends Searchable<Where>, What> { protected final Where where; protected final Locator<Where, What> locator; /** * Constructor of the Locating. * * @param where where * @param locator locator */ public Locating(Where where, Locator<Where, What> locator) { this.where = where; this.locator = locator; } public What locate() { return locator.locate(where); } }
And now Input is
public class Input<Where extends Searchable<Where>> extends Locating<Where, Element> { public static final Logger log = getLogger(Input.class); /** * Constructor of the input field. * * @param where where * @param selector selector */ public Input(Where where, Supplier<By> selector) { super(where, Locators.<Where>tryElement(selector)); } /** * the value of input field, for example, "good" will be return * <p> * String value = page.get(() -> By.name("status")) * <p> * <input name="status" value="good"/> * * @return the value of the input */ public String getValue() { final Retry retry = new Retry(5, 1, SECONDS); try { retry.attempt(() -> { log.info("{}", retry); Element element = locate(); return VALUE.locate(element); }); } catch (Exception e) { log.info("Failed to read text", e); } return null; } /** * set the value of input field, for example, * <p> * after, * page.set(() -> By.name("status"), "good"); * <p> * it will be, * <input name="status" value="good"/> * * @param value the value to set */ public void put(final Object value) { String string = value.toString(); final Retry retry = new Retry(5, 1, SECONDS); try { retry.attempt(() -> { log.info("{}", retry); Element element = locate(); element.clear(); element.sendKeys(string); if (VALUE.and(new IsStringEqual(string)).test(element)) { retry.off(); } return null; }); } catch (Exception e) { log.info("Failed to set text {}", string); } } /** * Test the autocomplete function for the input by given value, click the element * on the suggestion list which matches value parameter. * <p> * Please refer "http://seleniumcapsules.blogspot.com/2014/05/by-xpath.html" * * @param value value * @param locator locator */ public void autocomplete(Object value, Locator<Where, Element> locator) { Element element = locate(); element.clear(); Element suggestion; for (char c : value.toString().toCharArray()) { element.sendKeys(String.valueOf(c)); suggestion = locator.locate(where); if (suggestion != null) { suggestion.click(); return; } } suggestion = where.until(locator); if (suggestion != null) { suggestion.click(); } } }
Checkbox became,
public class Checkbox<Where extends Searchable<Where>> extends Locating<Where, Element> { /** * Constructor of the checkbox. * * @param where the place the checkbox can be found * @param selector the selector that leads to the checkbox */ public Checkbox(final Where where, Supplier<By> selector) { super(where, element(selector)); } /** * Change the checkbox according to the value parameter * * @param value true or false */ public void setValue(boolean value) { Element checkbox = locate(); if (checkbox != null && checkbox.isSelected() != value) { checkbox.click(); } } /** * @return whether the checkbox is checked or not */ public boolean isChecked() { return CHECKED.and(TRUE).test(locate()); } }
Radio became,
public class RadioButton<Where extends Searchable<Where>> extends Locating<Where, Stream&tl;Element>> { /** * Constructor this radio button. * * @param where where * @param selector selector */ public RadioButton(Where where, Supplier<By> selector) { super(where, elements(selector)); } /** * @param value value to set */ public void setValue(Object value) { new FirstMatch<>(DISPLAYED.and(VALUE.and(new IsStringEqual(value)))) .and(CLICK_IF_NOT_NULL) .locate(locate()); } /** * @return the value of the select radio */ public String getValue() { return new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))) .and(VALUE) .locate(locate()); } }
There no longer have these two instance fields,
protected final Locator
And
radioButtonGroup .and(new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE)))) .and(VALUE) .locate(where);
new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))) .and(VALUE) .locate(radioButtonGroup.locate(where));
VALUE.locate( new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))).locate( radioButtonGroup.locate(where)));
They all are equivalent to this sequential form,
Stream<Element> radios = radioButtonGroup.locate(where); Element radio = new FirstMatch<>(DISPLAYED.and(CHECKED.and(TRUE))).locate(radios); String value = VALUE.locate(radio);
which is exactly same to this raw form, without Selenium Capsules, you can see a lot of Selenium powder.
String value = null; List<WebElement> radios = webDriver.findElements(By.name("customFieldDS.customfield_ROW0_value")); for (WebElement radio : radios) { if (radio.getAttribute("checked").equals("true")) { value = radio.getAttribute("value")); } }
Friday, June 6, 2014
form element - select
Select is a dropdown user interface for people to select the desired the value from the list. for example,
Selenium UI Support provided a Select class which is great for users to manipulate select element on html page, however, it lacks a feature which will cause problem on AJAX enabled web pages. If the option list is built dynamically, selecting before it is populated will result in NoSuchElementException. Thus it requires adding waiting mechanism to the select before selecting an option from it. This waiting logic is encapsulated into a Select class in Selenium Capsules. The Select class takes a SelectLocator as parameter.
What it does is to find the Select element first, then wait for option list becomes populated and use it as a parameter for the constructor of the Select class in Selenium Capsules.
Unlike the selectByVisibleText in the Selenium UI Select class which takes String as parameter, this method in Selenium Capsules takes Object as parameter, so anything can be used as parameter, which make it possible to use enum, as long as the toString method of the enum to a string that can be found in the option list, then it can be used
And we add a method to FormControl as following,
Then we can use this method to select the dropdown.
If you don't want to use selectByVisibleText method you can simply create a Select object and use the other methods, all methods in Select class from selenium ui support are still available through the Select in Selenium Capsules, with some modification and additions, 1. parameter type from String to Object to enable enum as parameter, 2. additional method to accept enum as parameter and use its ordinal,
And CARD_TYPE is an enum instance to provide a Selenium By.id("card-type"), where Supplier is an interface since Jave 8.
Selenium UI Support provided a Select class which is great for users to manipulate select element on html page, however, it lacks a feature which will cause problem on AJAX enabled web pages. If the option list is built dynamically, selecting before it is populated will result in NoSuchElementException. Thus it requires adding waiting mechanism to the select before selecting an option from it. This waiting logic is encapsulated into a Select class in Selenium Capsules. The Select class takes a SelectLocator as parameter.
public class SelectLocator<Where extends Searchable<Where>> extends Locators<Where, Select> { private static final Logger log = getLogger(SelectLocator.class); public SelectLocator(Supplier<By> selector) { super((Where where) -> { final Element element = where.untilFound(selector); try { element.until(Locators.<Element>elements(OPTION).and(new HasElements<>())); return new Select(element); } catch (NoSuchElementException e) { element.click(); where.save(); log.error("Timeout waiting for the option list to populate.", e); throw e; } }); } }
What it does is to find the Select element first, then wait for option list becomes populated and use it as a parameter for the constructor of the Select class in Selenium Capsules.
public class Select<Where extends Searchable<Where>> { private static final Logger log = getLogger(Select.class); private final Where where; private final SelectLocator<Where> locator; /** * Constructor of the Select, It is a wrapper for the Select from Selenium UI. * @param where * @param locator */ public Select(Where where, SelectLocator<Where> locator) { this.where = where; this.locator = locator; } public void selectByVisibleText(Object text) { log.info("selecting select[" + locator + "] using [" + text + "]"); locator.locate(where).selectByVisibleText(text.toString()); } }
Unlike the selectByVisibleText in the Selenium UI Select class which takes String as parameter, this method in Selenium Capsules takes Object as parameter, so anything can be used as parameter, which make it possible to use enum, as long as the toString method of the enum to a string that can be found in the option list, then it can be used
public enum CreditCardType { American_Express, JCB, MasterCard, Visa, Discover; @Override public String toString() { return REPLACE_UNDERSCORE.locate(this.name()); } public static CreditCardType fromString(String string) { return valueOf(RESTORE_UNDERSCORE.locate(string)); } }
And we add a method to FormControl as following,
/** * Select the dropdown by given value. * * @param selector selector * @param value value */ @SuppressWarnings("unchecked") default public void select(Supplier<By> selector, Object value) { new Select<>((Where) this, Locators.<Where>select(selector)).selectByVisibleText(value); }
Then we can use this method to select the dropdown.
select(CARD_TYPE, CreditCardType.MasterCard);
If you don't want to use selectByVisibleText method you can simply create a Select object and use the other methods, all methods in Select class from selenium ui support are still available through the Select in Selenium Capsules, with some modification and additions, 1. parameter type from String to Object to enable enum as parameter, 2. additional method to accept enum as parameter and use its ordinal,
public void selectByEnumOrdinal(Enum index) { locate().selectByIndex(index.ordinal()); } public void selectByIndex(int index) { locate().selectByIndex(index); }
And CARD_TYPE is an enum instance to provide a Selenium By.id("card-type"), where Supplier is an interface since Jave 8.
public enum BookStoreId implements Supplier<By> { CARD_TYPE("card-type"); private final By by; private BookStoreId(String id) { this.by = id(id); } @Override public By get() { return by; } @Override public String toString() { return by.toString(); } }
Subscribe to:
Posts (Atom)