Thanks again to those of you who attended our recent webinar with Applitools on automated visual testing. If you want to share it or if you happened to miss it, you can catch the audio and slides here. We also worked with Selenium expert Dave Haeffner to provide the how-to on the subject. Enjoy his post below.
The Problem
In previous
Do you need to write and maintain a separate set of tests? What about your existing Selenium tests? What do you do if there isn't a sufficient library for the programming language you're currently using?
A Solution
You can rest easy knowing that you can build automated visual testing checks into your existing Selenium tests. By leveraging a third-party platform like Applitools Eyes, this is a simple feat.
And when coupled with Sauce Labs, you can quickly add coverage for those hard to reach browser, device, and platform combinations.
Let's step through an example.
An Example
NOTE: This example is written in Java with the JUnit testing framework.
Let's start with an existing Selenium test. A simple one that logs into a website.
// filename: Login.java import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; public class Login { private WebDriver driver; @Before public void setup() { driver = new FirefoxDriver(); } @Test public void succeeded() { driver.get("http://the-internet.herokuapp.com/login"); driver.findElement(By.id("username")).sendKeys("tomsmith"); driver.findElement(By.id("password")).sendKeys("SuperSecretPassword!"); driver.findElement(By.id("login")).submit(); Assert.assertTrue(";success message should be present after logging in", driver.findElement(By.cssSelector".flash.success")).isDisplayed()); } @After public void teardown() { driver.quit(); } |
In it we're loading an instance of Firefox, visiting the login page on
Now let's add in Applitools Eyes support.
If you haven't already done so, you'll need to create a free Applitools Eyes account (no
// filename: pom.xml |
Next, we'll need to add a variable (to store the instance of Applitools Eyes) and modify our test setup.
private WebDriver driver; private Eyes eyes; @Before public void setup() { WebDriver browser = new FirefoxDriver(); eyes = new Eyes(); eyes.setApiKey("YOUR_APPLITOOLS_API_KEY"); driver = eyes.open(browser, "the-internet", "Login succeeded"); } |
Rather than storing the Selenium instance in driver
browser
eyes.open
-- storing the WebDriver object that eyes.open
returns in driver
This way the Eyes platform will be able to capture what our test is doing when we ask it to capture a screenshot. The Selenium actions in our test will not need to be modified.
Before eyes.open
eyes.open
"the-internet"
), and the name of the test (e.g., "Login succeeded"
).
Now we're ready to add some visual checks to our test.
// filename: Login.java ... @Test public void succeeded() { driver.get("http://the-internet.herokuapp.com/login"); eyes.checkWindow("Login"); driver.findElement(By.id("username")).sendKeys("tomsmith"); driver.findElement(By.id("password")).sendKeys("SuperSecretPassword!"); driver.findElement(By.id("login")).submit(); eyes.checkWindow("Logged In"); Assert.assertTrue("success message should be present after logging in", driver.findElement(By.cssSelector(".flash.success")).isDisplayed()); eyes.close(); } ... |
eyes.checkWindow();
eyes.checkWindow();
NOTE: These visual checks are effectively doing the same work as the pre-existing assertion (e.g., where we're asking Selenium if a success notification is displayed and asserting on the Boolean result) -- in addition to reviewing other visual aspects of the page. So once we verify that our test is working correctly we can remove this assertion and still be covered.
We end the test eyes.close
teardown
eyes.close
NOTE: An exceptions eyes.close
When an exception gets thrown eyes.close
eyes.close
teardown
// filename: Login.java ... @After public void teardown() { eyes.abortIfNotClosed(); driver.quit(); } } |
will make sure the Eyes session terminates properly regardless of what happens in the test.
eyes.abortIfNotClosed();
Now when we run the test, it will execute locally while also performing visual checks in Applitools Eyes.
What About Other Browsers?
If we want to run our test with its newly added visual checks against other browsers and operating systems, it's simple enough to run your cross-browser tests on Sauce Labs.
NOTE: If you don't already have a Sauce Labs account, sign up for a free trial account here.
// filename: Login.java ... import org.openqa.selenium.Platform; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; import java.net.URL; ... |
We'll then need to modify the test setup to load a Sauce browser instance (via Selenium Remote) instead of a local Firefox one.
// filename: Login.java ... @Before public void setup() throws Exception { DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer(); capabilities.setCapability("platform", Platform.XP); capabilities.setCapability("version", "8"); capabilities.setCapability("name", "Login succeeded"); String sauceUrl = String.format( "http://%s:%s@ondemand.saucelabs.com:80/wd/hub", "YOUR_SAUCE_USERNAME", "YOUR_SAUCE_ACCESS_KEY"); WebDriver browser = new RemoteWebDriver(new URL(sauceUrl), capabilities); eyes = new Eyes(); eyes.setApiKey(System.getenv("APPLITOOLS_API_KEY")); driver = eyes.open(browser, "the-internet", "Login succeeded"); } ... |
We tell Sauce what we want in our test instance DesiredCapabilities
In order to connect to Sauce, we need to provide an account username and access key. The access key can be found on your account page. These values get concatenated into a URL that points to the Sauce Labs on-demand grid.
Once we have DesiredCapabilities
browser
browser
eyes.open
driver
Now when we run this test, it will execute against Internet Explorer 8 on Windows XP. You can see the test while it's running in your Sauce Labs account dashboard. And you can see the images captured on your Applitools account dashboard.
A Small Bit Of Cleanup
Both Applitools and Sauce Labs require you to specify a test name. Up until now, we've been hard-coding a value. Let's change it so it gets set automatically.
We can do this by leveraging a TestWatcher
// filename: Login.java ... import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; ... public class Login { private WebDriver driver; private Eyes eyes; public String testName; @Rule public TestRule watcher = new TestWatcher() { protected void starting(Description description) { testName = description.getDisplayName(); } }; |
Each time a test starts, TestWatcher
starting
testName
Let's clean up our setup to use this variable instead of a hard-coded value.
// filename: Login.java ... @Before public void setup() throws Exception { DesiredCapabilities capabilities = DesiredCapabilities.internetExplorer(); capabilities.setCapability("platform", Platform.XP); capabilities.setCapability("version", "8"); capabilities.setCapability("name", testName); String sauceUrl = String.format( "http://%s:%s@ondemand.saucelabs.com:80/wd/hub", System.getenv("SAUCE_USERNAME"), System.getenv("SAUCE_ACCESS_KEY")); WebDriver browser = new RemoteWebDriver(new URL(sauceUrl), capabilities); eyes = new Eyes(); eyes.setApiKey(System.getenv("APPLITOOLS_API_KEY")); driver = eyes.open(browser, "the-internet", testName); } ... |
Now when we run our test, the name will automatically appear. This will come in handy with additional tests.
One More Thing
When a job fails in Applitools Eyes, it automatically returns a URL for it in the test output. It would be nice if we could also get the Sauce Labs job URL in the output. So let's add it.
First, we'll need a public variable to store the session ID of the Selenium job.
// filename: Login.java ... public class Login { private WebDriver driver; private Eyes eyes; public String testName; public String sessionId; ... |
TestWatcher
// filename: Login.java
...
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
testName = description.getDisplayName();
}
@Override
protected void failed(Throwable e, Description description) {
System.out.println(String.format("https://saucelabs.com/tests/%s", sessionId));
}
};
...
|
Lastly, we'll grab the session ID from the Sauce browser instance just after it's created.
// filename: Login.java ... WebDriver browser = new RemoteWebDriver(new URL(sauceUrl), capabilities); sessionId = ((RemoteWebDriver) browser).getSessionId().toString(); ... |
Now when we run our test, if there's a Selenium failure, a URL to the Sauce job will be returned in the test output.
Expected Outcome
- Connect to Applitools Eyes
- Load an instance of Selenium in Sauce Labs
- Run the test, performing visual checks at specified points
- Close the Applitools session
- Close the Sauce Labs session
- Return a URL to a failed job in either Applitools Eyes or Sauce Labs
Outro
Happy Testing!
About Dave Haeffner: Dave is the author of Elemental Selenium (a free, once weekly Selenium tip newsletter that is read by hundreds of testing professionals) as well as a new book, The Selenium Guidebook. He is also the creator and maintainer of ChemistryKit (an open-source Selenium framework). He has helped numerous companies successfully implement automated acceptance testing; including The Motley Fool, ManTech International, Sittercity, and Animoto. He is a founder and co-organizer of the Selenium Hangout and has spoken at numerous conferences and meetups about acceptance testing.