br.eti.kinoshita.selenium.SeleniumWebTest.java Source code

Java tutorial

Introduction

Here is the source code for br.eti.kinoshita.selenium.SeleniumWebTest.java

Source

/* 
 * 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);
    }

}