org.opennms.smoketest.OpenNMSSeleniumTestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.opennms.smoketest.OpenNMSSeleniumTestCase.java

Source

/*******************************************************************************
 * This file is part of OpenNMS(R).
 *
 * Copyright (C) 2011-2014 The OpenNMS Group, Inc.
 * OpenNMS(R) is Copyright (C) 1999-2014 The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * OpenNMS(R) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with OpenNMS(R).  If not, see:
 *      http://www.gnu.org/licenses/
 *
 * For more information contact:
 *     OpenNMS(R) Licensing <license@opennms.org>
 *     http://www.opennms.org/
 *     http://www.opennms.com/
 *******************************************************************************/

package org.opennms.smoketest;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.NoAlertPresentException;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.phantomjs.PhantomJSDriver;
import org.openqa.selenium.phantomjs.PhantomJSDriverService;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.io.Files;
import com.thoughtworks.selenium.SeleniumException;

public class OpenNMSSeleniumTestCase {
    private static final Logger LOG = LoggerFactory.getLogger(OpenNMSSeleniumTestCase.class);

    public static final long LOAD_TIMEOUT = Long.getLong("org.opennms.smoketest.web-timeout", 120000l);
    public static final String OPENNMS_WEB_HOST = System.getProperty("org.opennms.smoketest.web-host", "localhost");
    public static final int OPENNMS_WEB_PORT = Integer.getInteger("org.opennms.smoketest.web-port", 8980);
    public static final String OPENNMS_EVENT_HOST = System.getProperty("org.opennms.smoketest.event-host",
            OPENNMS_WEB_HOST);
    public static final int OPENNMS_EVENT_PORT = Integer.getInteger("org.opennms.smoketest.event-port", 5817);

    public static final String BASE_URL = "http://" + OPENNMS_WEB_HOST + ":" + OPENNMS_WEB_PORT + "/";
    public static final String REQUISITION_NAME = "SeleniumTestGroup";
    public static final String USER_NAME = "SmokeTestUser";
    public static final String GROUP_NAME = "SmokeTestGroup";

    protected static final boolean usePhantomJS = Boolean.getBoolean(
            "org.opennms.smoketest.webdriver.use-phantomjs") || Boolean.getBoolean("smoketest.usePhantomJS");

    protected WebDriver m_driver = null;
    protected WebDriverWait wait = null;

    @Rule
    public TestWatcher m_watcher = new TestWatcher() {
        @Override
        protected void starting(final Description description) {
            try {
                m_driver = getDriver();
                LOG.debug("Using driver: {}", m_driver);
                m_driver.manage().timeouts().implicitlyWait(LOAD_TIMEOUT, TimeUnit.MILLISECONDS);
                wait = new WebDriverWait(m_driver, TimeUnit.SECONDS.convert(LOAD_TIMEOUT, TimeUnit.MILLISECONDS));

                m_driver.get(BASE_URL + "opennms/login.jsp");
                wait.until(ExpectedConditions.visibilityOfElementLocated(By.name("j_username")));
                enterText(By.name("j_username"), "admin");
                enterText(By.name("j_password"), "admin");
                findElementByName("Login").click();
                wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//div[@id='content']")));
            } catch (final InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                LOG.debug("Failed to get driver", e);
                throw new RuntimeException("Tests aren't going to work.  Bailing.");
            }
        }

        @Override
        protected void failed(final Throwable e, final Description description) {
            final String testName = description.getMethodName();
            LOG.debug("Test {} failed... attempting to take screenshot.", testName);
            if (m_driver != null && m_driver instanceof TakesScreenshot) {
                final TakesScreenshot shot = (TakesScreenshot) m_driver;
                try {
                    final File from = shot.getScreenshotAs(OutputType.FILE);
                    final String screenshotFileName = "target" + File.separator + "screenshots" + File.separator
                            + description.getClassName() + "." + testName + ".png";
                    final File to = new File(screenshotFileName);
                    LOG.debug("Screenshot saved to: {}", from);
                    try {
                        to.getParentFile().mkdirs();
                        Files.move(from, to);
                        LOG.debug("Screenshot moved to: {}", to);
                    } catch (final IOException ioe) {
                        LOG.debug("Failed to move screenshot from {} to {}", from, to, ioe);
                    }
                } catch (final Exception sse) {
                    LOG.debug("Failed to take screenshot.", sse);
                }
            } else {
                LOG.debug("Driver can't take screenshots.");
            }
            LOG.debug("Current URL: {}", m_driver.getCurrentUrl());
            m_driver.navigate().back();
            LOG.debug("Previous URL: {}", m_driver.getCurrentUrl());
        }

        @Override
        protected void finished(final Description description) {
            try {
                deleteTestRequisition();
                deleteTestUser();
                deleteTestGroup();
            } catch (final Exception e) {
                LOG.error("Cleaning up failed. Future tests will be in an unhandled state.", e);
            }

            LOG.debug("Shutting down Selenium.");
            if (m_driver != null) {
                try {
                    m_driver.get(BASE_URL + "opennms/j_spring_security_logout");
                } catch (final SeleniumException e) {
                    // don't worry about it, this is just for logging out
                }
                try {
                    m_driver.quit();
                } catch (final Exception e) {
                    LOG.error("Failed while shutting down WebDriver for test {}.", description.getMethodName(), e);
                }
                m_driver = null;
            }

            try {
                Thread.sleep(3000);
            } catch (final InterruptedException e) {
            }
        }
    };

    @BeforeClass
    public static void configureLogging() throws Exception {
        final String logLevel = System.getProperty("org.opennms.smoketest.logLevel", "DEBUG");
        final Logger logger = org.slf4j.LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
        if (logger instanceof ch.qos.logback.classic.Logger) {
            final ch.qos.logback.classic.Logger logbackLogger = (ch.qos.logback.classic.Logger) logger;
            logbackLogger.setLevel(ch.qos.logback.classic.Level.valueOf(logLevel));
        }
    }

    protected WebDriver getDriver() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        WebDriver driver = null;
        final String driverClass = System.getProperty("org.opennms.smoketest.webdriver.class",
                System.getProperty("webdriver.class"));
        if (driverClass != null) {
            driver = (WebDriver) Class.forName(driverClass).newInstance();
        }

        // otherwise, PhantomJS if found, or fall back to Firefox
        if (driver == null) {
            if (usePhantomJS) {
                final File phantomJS = findPhantomJS();
                if (phantomJS != null) {
                    final DesiredCapabilities caps = new DesiredCapabilities();
                    caps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,
                            phantomJS.toString());
                    driver = new PhantomJSDriver(caps);
                }
            }
            if (driver == null) {
                driver = new FirefoxDriver();
            }
        }
        return driver;
    }

    private File findPhantomJS() {
        final String os = System.getProperty("os.name").toLowerCase();
        final String extension = (os.indexOf("win") >= 0) ? ".exe" : "";

        final String path = System.getenv("PATH");
        if (path == null) {
            LOG.debug("findPhantomJS(): Unable to get PATH.");
            final File phantomFile = new File("/usr/local/bin/phantomjs" + extension);
            LOG.debug("findPhantomJS(): trying {}", phantomFile);
            if (phantomFile.exists() && phantomFile.canExecute()) {
                return phantomFile;
            }
        } else {
            final List<String> paths = new ArrayList<String>(Arrays.asList(path.split(File.pathSeparator)));
            paths.add("/usr/local/bin");
            paths.add("/usr/local/sbin");
            LOG.debug("findPhantomJS(): paths = {}", paths);
            for (final String directory : paths) {
                final File phantomFile = new File(directory + File.separator + "phantomjs" + extension);
                LOG.debug("findPhantomJS(): trying {}", phantomFile);
                if (phantomFile.exists() && phantomFile.canExecute()) {
                    return phantomFile;
                }
            }
        }
        return null;
    }

    protected WebDriverWait waitFor(final long seconds) {
        return new WebDriverWait(m_driver, seconds);
    }

    protected ExpectedCondition<Boolean> pageContainsText(final String text) {
        final String escapedText = text.replace("\'", "\\\'");
        return new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(final WebDriver driver) {
                final String xpathExpression = "//*[contains(., '" + escapedText + "')]";
                LOG.debug("XPath expression: {}", xpathExpression);
                final WebElement element = driver.findElement(By.xpath(xpathExpression));
                return element != null;
            }
        };
    }

    protected String handleAlert() {
        try {
            final Alert alert = m_driver.switchTo().alert();
            final String alertText = alert.getText();
            alert.dismiss();
            return alertText;
        } catch (final NoAlertPresentException e) {
            LOG.debug("handleAlert: no alert is active");
        }
        return null;
    }

    protected void setChecked(final By by) {
        final WebElement element = m_driver.findElement(by);
        if (element.isSelected()) {
            return;
        } else {
            element.click();
        }
    }

    protected void setUnchecked(final By by) {
        final WebElement element = m_driver.findElement(by);
        if (element.isSelected()) {
            element.click();
        } else {
            return;
        }
    }

    protected void clickMenuItem(final String menuItemText, final String submenuItemText,
            final String submenuItemHref) {
        final Actions action = new Actions(m_driver);

        final WebElement menuElement;
        if (menuItemText.startsWith("name=")) {
            final String menuItemName = menuItemText.replaceFirst("name=", "");
            menuElement = findElementByName(menuItemName);
        } else {
            menuElement = findElementByXpath("//a[contains(text(), '" + menuItemText + "')]");
        }
        action.moveToElement(menuElement, 2, 2).perform();

        final WebElement submenuElement;
        if (submenuItemText != null) {
            if (submenuItemHref == null) {
                submenuElement = findElementByXpath("//a[contains(text(), '" + submenuItemText + "')]");
            } else {
                submenuElement = findElementByXpath(
                        "//a[@href='" + submenuItemHref + "' and contains(text(), '" + submenuItemText + "')]");
            }
        } else {
            submenuElement = null;
        }

        if (submenuElement == null) {
            // no submenu given, just click the main element
            // wait until the element is visible, not just present in the DOM
            wait.until(ExpectedConditions.visibilityOf(menuElement));
            menuElement.click();
        } else {
            // we want a submenu item, click it instead
            // wait until the element is visible, not just present in the DOM
            wait.until(ExpectedConditions.visibilityOf(submenuElement));
            submenuElement.click();
        }
    }

    protected void frontPage() {
        m_driver.get(BASE_URL + "opennms/");
        m_driver.findElement(By.id("index-contentleft"));
    }

    public void adminPage() {
        m_driver.get(BASE_URL + "opennms/admin/index.jsp");
    }

    protected void nodePage() {
        m_driver.get(BASE_URL + "opennms/element/nodeList.htm");
    }

    protected void notificationsPage() {
        m_driver.get(BASE_URL + "opennms/notification/index.jsp");
    }

    protected void outagePage() {
        m_driver.get(BASE_URL + "opennms/outage/index.jsp");
    }

    protected void provisioningPage() {
        m_driver.get(BASE_URL + "opennms/admin/index.jsp");
        m_driver.findElement(By.linkText("Manage Provisioning Requisitions")).click();
    }

    protected void reportsPage() {
        m_driver.get(BASE_URL + "opennms/report/index.jsp");
    }

    protected void searchPage() {
        m_driver.get(BASE_URL + "opennms/element/index.jsp");
    }

    protected void supportPage() {
        m_driver.get(BASE_URL + "opennms/support/index.htm");
    }

    protected void goBack() {
        LOG.warn("goBack() is supposedly not supported on Safari!");
        m_driver.navigate().back();
    }

    public WebElement findElementById(final String id) {
        return m_driver.findElement(By.id(id));
    }

    public WebElement findElementByLink(final String link) {
        return m_driver.findElement(By.linkText(link));
    }

    public WebElement findElementByName(final String name) {
        return m_driver.findElement(By.name(name));
    }

    public WebElement findElementByXpath(final String xpath) {
        return m_driver.findElement(By.xpath(xpath));
    }

    protected WebElement enterText(final By selector, final String text) {
        LOG.debug("Enter text: '{}' into selector: {}", text, selector);
        final WebElement element = m_driver.findElement(selector);
        element.clear();
        element.sendKeys(text);
        return element;
    }

    private Integer doRequest(final HttpRequestBase request)
            throws ClientProtocolException, IOException, InterruptedException {
        final CountDownLatch waitForCompletion = new CountDownLatch(1);

        final URI uri = request.getURI();
        final HttpHost targetHost = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
        CredentialsProvider credsProvider = new BasicCredentialsProvider();
        credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()),
                new UsernamePasswordCredentials("admin", "admin"));
        AuthCache authCache = new BasicAuthCache();
        // Generate BASIC scheme object and add it to the local auth cache
        BasicScheme basicAuth = new BasicScheme();
        authCache.put(targetHost, basicAuth);

        // Add AuthCache to the execution context
        HttpClientContext context = HttpClientContext.create();
        context.setCredentialsProvider(credsProvider);
        context.setAuthCache(authCache);

        final CloseableHttpClient client = HttpClients.createDefault();

        final ResponseHandler<Integer> responseHandler = new ResponseHandler<Integer>() {
            @Override
            public Integer handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {
                try {
                    final int status = response.getStatusLine().getStatusCode();
                    // 400 because we return that if you try to delete something that is already deleted
                    // 404 because it's OK if it's already not there
                    if (status >= 200 && status < 300 || status == 400 || status == 404) {
                        EntityUtils.consume(response.getEntity());
                        return status;
                    } else {
                        throw new ClientProtocolException("Unexpected response status: " + status);
                    }
                } finally {
                    waitForCompletion.countDown();
                }
            }
        };

        final Integer status = client.execute(targetHost, request, responseHandler, context);

        waitForCompletion.await();
        client.close();
        return status;
    }

    public void deleteExistingRequisition(final String foreignSource) {
        provisioningPage();

        LOG.debug("deleteExistingRequisition: Deleting Requisition: {}", foreignSource);
        if (getForeignSourceElement(foreignSource) == null) {
            LOG.debug("deleteExistingRequisition: Requisition {} is already gone.", foreignSource);
            return;
        }

        do {
            long nodesInRequisition = -1;
            long nodesInDatabase = -1;

            try {
                m_driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);

                final WebElement foreignSourceElement = getForeignSourceElement(foreignSource);

                nodesInRequisition = getNodesInRequisition(foreignSourceElement);
                nodesInDatabase = getNodesInDatabase(foreignSourceElement);

                LOG.debug("deleteExistingRequisition: nodesInRequisition={}, nodesInDatabase={}",
                        nodesInRequisition, nodesInDatabase);

                if (nodesInDatabase > 0) {
                    if (nodesInRequisition > 0) {
                        LOG.debug("deleteExistingRequisition: We have requisitioned nodes, deleting them.");
                        try {
                            final WebElement deleteNodesButton = foreignSourceElement
                                    .findElement(By.xpath("//input[@type='button' and @value='Delete Nodes']"));
                            deleteNodesButton.click();
                            wait.until(ExpectedConditions.alertIsPresent()).accept();
                        } catch (final NoSuchElementException e) {
                        }
                    } else {
                        LOG.debug(
                                "deleteExistingRequisition: We have no requisitioned nodes, but there are nodes in the database. Synchronizing.");
                        final WebElement synchronizeButton = foreignSourceElement
                                .findElement(By.xpath("//input[@type='button' and @value='Synchronize']"));
                        synchronizeButton.click();
                        wait.until(new WaitForNodesInDatabase(0));
                    }
                } else {
                    // no nodes in the database
                    try {
                        LOG.debug(
                                "deleteExistingRequisition: We have no nodes in the database, time to delete the requisition. PUSH THE BUTTON FRANK.");
                        final WebElement deleteRequisitionButton = foreignSourceElement
                                .findElement(By.xpath("//input[@type='button' and @value='Delete Requisition']"));
                        deleteRequisitionButton.click();
                    } catch (final NoSuchElementException e) {
                    }
                }
            } finally {
                m_driver.manage().timeouts().implicitlyWait(LOAD_TIMEOUT, TimeUnit.MILLISECONDS);
            }
        } while (getForeignSourceElement(foreignSource) != null);
    }

    protected WebElement getForeignSourceElement(final String requisitionName) {
        final String selector = "//span[@data-foreignSource='" + requisitionName + "']";
        WebElement foreignSourceElement = null;
        try {
            m_driver.manage().timeouts().implicitlyWait(2, TimeUnit.SECONDS);
            foreignSourceElement = m_driver.findElement(By.xpath(selector));
        } catch (final NoSuchElementException e) {
            // no match, treat as a no-op
            LOG.debug("Could not find: {}", selector);
            return null;
        } finally {
            m_driver.manage().timeouts().implicitlyWait(LOAD_TIMEOUT, TimeUnit.MILLISECONDS);
        }
        return foreignSourceElement;
    }

    protected void deleteTestRequisition() throws Exception {
        final Integer responseCode = doRequest(
                new HttpGet(BASE_URL + "/opennms/rest/requisitions/" + REQUISITION_NAME));
        LOG.debug("Checking for existing test requisition: {}", responseCode);
        if (responseCode == 404 || responseCode == 204) {
            LOG.debug("deleteTestRequisition: already deleted");
            return;
        }

        deleteExistingRequisition(REQUISITION_NAME);
        doRequest(new HttpDelete(BASE_URL + "/opennms/rest/requisitions/" + REQUISITION_NAME));
        doRequest(new HttpDelete(BASE_URL + "/opennms/rest/requisitions/deployed/" + REQUISITION_NAME));
        doRequest(new HttpGet(BASE_URL + "/opennms/rest/requisitions"));
    }

    protected void deleteTestUser() throws Exception {
        doRequest(new HttpDelete(BASE_URL + "/opennms/rest/users/" + USER_NAME));
    }

    protected void deleteTestGroup() throws Exception {
        doRequest(new HttpDelete(BASE_URL + "/opennms/rest/groups/" + GROUP_NAME));
    }

    protected long getNodesInRequisition(final WebElement element) {
        try {
            final WebElement match = element.findElement(By.xpath("//span[@data-requisitionedNodes]"));
            final String nodes = match.getAttribute("data-requisitionedNodes");
            if (nodes != null) {
                final Long nodeCount = Long.valueOf(nodes);
                LOG.debug("{} requisitioned nodes found.", nodeCount);
                return nodeCount;
            }
        } catch (final NoSuchElementException e) {
        }
        LOG.debug("0 requisitioned nodes found.");
        return 0;
    }

    protected long getNodesInDatabase(final WebElement element) {
        try {
            final WebElement match = element.findElement(By.xpath("//span[@data-databaseNodes]"));
            final String nodes = match.getAttribute("data-databaseNodes");
            if (nodes != null) {
                final Long nodeCount = Long.valueOf(nodes);
                LOG.debug("{} database nodes found.", nodeCount);
                return nodeCount;
            }
        } catch (final NoSuchElementException e) {
        }
        LOG.debug("0 database nodes found.");
        return 0;
    }

    protected final class WaitForNodesInDatabase implements ExpectedCondition<Boolean> {
        private final int m_numberToMatch;

        public WaitForNodesInDatabase(int numberOfNodes) {
            m_numberToMatch = numberOfNodes;
        }

        @Override
        public Boolean apply(final WebDriver input) {
            provisioningPage();
            final long nodes = getNodesInDatabase(getForeignSourceElement(REQUISITION_NAME));
            if (nodes == m_numberToMatch) {
                return true;
            } else {
                return null;
            }
        }
    }

}