Java tutorial
/* * The MIT License * * Copyright (c) 2011 Bruno P. Kinoshita <http://www.kinoshita.eti.br> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package br.eti.kinoshita.selenium; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.util.LinkedHashMap; import java.util.Map; import org.apache.commons.codec.binary.Base64; import org.apache.commons.configuration.Configuration; import org.apache.commons.io.FileUtils; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.tap4j.ext.testng.TAPAttribute; import org.tap4j.ext.testng.TestTAPReporter; import org.testng.Assert; import org.testng.ITestContext; import org.testng.annotations.AfterTest; import org.testng.annotations.Guice; import org.testng.annotations.Listeners; import br.eti.kinoshita.selenium.model.SeleniumScreenshot; import com.google.inject.Inject; /** * <p>This is the base class for TestNG tests that generate TAP Streams (check * the annotation over the class name) and control a Selenium WebDriver.</p> * * <p>It contains a static WebDriver and a static Configuration (from * Apache commons). You will have a single WebDriver during your whole test * execution. It probably won't be a problem since the driver itself is not * Thread-Safe.</p> * * <p>The configuration is a composite configuration, consisting of * selenium.properties file properties and system properties. The system * properties override those from selenium.properties. This may be useful * speciially when running your tests in Maven, Jenkins or via * command line. The selenium.properties file is loaded using the classloader, * then you can replace it with a new one from your project. In case you are * using Maven, just create a selenium.properties file in src/main/resources, * or src/test/resources.</p> * * <p>Beware of iframe applications (like those generated by GWT-like * frameworks ;). You will have to switch from one driver to another one many * times. Helpful methods may be found in classes in the * br.eti.kinoshita.selenium.util package. <u>May the force be with you * my friend.</u>.</p> * * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br * @author Cesar Fernandes de Almeida * @since 0.1 */ @Listeners(value = TestTAPReporter.class) @Guice(modules = { SeleniumGuiceModule.class }) public abstract class SeleniumWebTest { protected final static Logger LOGGER = LoggerFactory.getLogger(SeleniumWebTest.class); /** * The WebDriver instance used throughout our tests. */ @Inject protected WebDriver driver; @Inject protected Configuration configuration; /** * Closes the driver and quit. This method is annotated to always run. */ @AfterTest(alwaysRun = true) public void tearDown() { if (driver != null) { LOGGER.info("Closing WebDriver..."); try { driver.close(); driver.quit(); LOGGER.info("OK!"); } catch (Throwable t) { LOGGER.warn(t.getMessage(), t); } } } /** * Gets the Selenium Web Test configuration. */ public Configuration getConfiguration() { return configuration; } /** * <p>Adds a screen shot to the list of attributes. TestNG has a strange * behavior when adding attributes to a context. Although I am adding * attributes to a context from within a method, it does not maintain * your attributes separated per method. So, say you want to add the * same attribute in different methods. You will have a hard time debugging * until you realize sometimes it is simply replacing your attributes. * Bummer.</p> * * <p>We are adding all screen shots as image/png. This may lead to * troubles in the future, so probably it will change soon. The * title of your screen shot will be its name (no creativity, sorry).</p> * * <p>TBD: Check if we can add a way to pass the file type as parameter.</p> * * @param context TestNG test context. * @param method TestNG test method, to which we will link your attribute to. * @param description Screen shot description. */ public void addScreenShot(ITestContext context, Method method, String description) { if (driver instanceof TakesScreenshot) { LOGGER.debug("Taking screenshot with driver " + driver.getTitle()); File attachment = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); SeleniumScreenshot screenshot = new SeleniumScreenshot(attachment, description, "image/png", attachment.getName()); this.addScreenShot(context, method, screenshot); } else { // We know usually the user wouldn't be able to use a HTML driver as // we are checking in the static constructor, however I am paranoid // with me users :) LOGGER.warn("Driver " + driver.getTitle() + " does not support taking screenshots. Use a different one please."); } } /** * <p>In this method, your screen shot will be converted into a TAP Stream * and then put into a TAPAttribute object. This object will be stored in * TestNG Test Context.</p> * * <p>Later, TestTAPReporter from tap4j.org project has the logic to * transform it into a YAMLish diagnostic entry in your TAP Stream. From * this point on, the limit to where, how, when it will be used is your * imagination.</p> * * @see {@link TAPAttribute} * @see {@link TestTAPReporter} * @see <a href="http://www.testanything.org">Test Anything Protocol</a> * * @param context TestNG test context. * @param method TestNG test method, to which we will link your attribute to. * @param screenshot A screen shot, that will be added to a TAPAttribute. */ @SuppressWarnings("unchecked") protected void addScreenShot(ITestContext context, Method method, SeleniumScreenshot screenshot) { final Object o = context.getAttribute("Files"); Map<String, Object> filesMap = null; if (o == null) { filesMap = new LinkedHashMap<String, Object>(); } else { TAPAttribute attr = (TAPAttribute) o; if (attr.getMethod() != method) { filesMap = new LinkedHashMap<String, Object>(); } else { filesMap = (Map<String, Object>) attr.getValue(); } } final Map<String, Object> fileMap = new LinkedHashMap<String, Object>(); final File file = screenshot.getFile(); // These properties were not randomly chosen. Some of them were // defined in a TAP Wiki page. I know I have the link is somewhere here... fileMap.put("File-Location", file.getAbsolutePath()); fileMap.put("File-Title", screenshot.getTitle()); fileMap.put("File-Description", screenshot.getDescription()); fileMap.put("File-Size", file.length()); fileMap.put("File-Name", file.getName()); byte[] fileData = null; try { fileData = FileUtils.readFileToByteArray(file); } catch (IOException e) { Assert.fail("Failed to read file to byte array.", e); } final String content = Base64.encodeBase64String(fileData); fileMap.put("File-Content", content); fileMap.put("File-Type", screenshot.getFileType()); filesMap.put(file.getAbsolutePath(), fileMap); final TAPAttribute attribute = new TAPAttribute(method, filesMap); context.setAttribute("Files", attribute); } }