com.sonar.it.jenkins.orchestrator.JenkinsOrchestrator.java Source code

Java tutorial

Introduction

Here is the source code for com.sonar.it.jenkins.orchestrator.JenkinsOrchestrator.java

Source

/*
 * Jenkins :: Integration Tests
 * Copyright (C) 2013 ${owner}
 * sonarqube@googlegroups.com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
 */
package com.sonar.it.jenkins.orchestrator;

import com.google.common.base.Function;
import com.sonar.it.jenkins.orchestrator.container.JenkinsDistribution;
import com.sonar.it.jenkins.orchestrator.container.JenkinsServer;
import com.sonar.it.jenkins.orchestrator.container.JenkinsWrapper;
import com.sonar.it.jenkins.orchestrator.container.ServerInstaller;
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.BuildResult;
import com.sonar.orchestrator.build.SonarScannerInstaller;
import com.sonar.orchestrator.config.Configuration;
import com.sonar.orchestrator.config.FileSystem;
import com.sonar.orchestrator.container.Server;
import com.sonar.orchestrator.junit.SingleStartExternalResource;
import com.sonar.orchestrator.locator.Location;
import com.sonar.orchestrator.util.NetworkUtils;
import com.sonar.orchestrator.version.Version;
import hudson.cli.CLI;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Nullable;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.output.WriterOutputStream;
import org.apache.commons.lang.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
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.firefox.FirefoxProfile;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.wsclient.jsonsimple.JSONValue;
import org.sonar.wsclient.qualitygate.QualityGates;

import static org.fest.assertions.Assertions.assertThat;

public class JenkinsOrchestrator extends SingleStartExternalResource {
    private static final Logger LOG = LoggerFactory.getLogger(JenkinsOrchestrator.class);

    private final Configuration config;
    private final JenkinsDistribution distribution;
    private final AtomicBoolean started = new AtomicBoolean(false);
    private JenkinsWrapper jenkinsWrapper;
    private JenkinsServer server;
    private WebDriver driver;
    private CLI cli;
    private static int tokenCounter = 0;

    JenkinsOrchestrator(Configuration config, JenkinsDistribution distribution) {
        this.config = config;
        this.distribution = distribution;
    }

    @Override
    protected void beforeAll() {
        start();
    }

    @Override
    protected void afterAll() {
        stop();
    }

    public void start() {
        if (started.getAndSet(true)) {
            throw new IllegalStateException("Jenkins is already started");
        }

        int port = config.getInt("jenkins.container.port", 0);
        if (port <= 0) {
            port = NetworkUtils.getNextAvailablePort();
        }
        distribution.setPort(port);
        FileSystem fileSystem = config.fileSystem();
        ServerInstaller serverInstaller = new ServerInstaller(fileSystem);
        server = serverInstaller.install(distribution);
        server.setUrl(String.format("http://localhost:%d", port));

        jenkinsWrapper = new JenkinsWrapper(server, config, fileSystem.javaHome(), port);
        jenkinsWrapper.start();

        FirefoxProfile profile = new FirefoxProfile();
        // Disable native events on all OS to avoid strange characters when using sendKeys
        profile.setEnableNativeEvents(false);
        driver = new FirefoxDriver(profile);

        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        driver.get(server.getUrl());

        // Fix window size for having reproducible test behavior
        driver.manage().window().setPosition(new Point(20, 20));
        driver.manage().window().setSize(new Dimension(1024, 768));
        try {
            cli = new CLI(new URL(server.getUrl()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void stop() {
        if (!started.getAndSet(false)) {
            // ignore double-stop
            return;
        }

        driver.quit();
        try {
            cli.close();
        } catch (Exception e) {
            LOG.warn("Error while stopping CLI", e);
        }

        if (jenkinsWrapper != null) {
            jenkinsWrapper.stopAndClean();
        }
    }

    public Configuration getConfiguration() {
        return config;
    }

    public JenkinsServer getServer() {
        return server;
    }

    public JenkinsDistribution getDistribution() {
        return distribution;
    }

    /**
     * Use environment variables and system properties
     */
    public static JenkinsOrchestrator createEnv(Version sqJenkinsPluginVersion) {
        return new JenkinsOrchestratorBuilder(Configuration.createEnv()).build();
    }

    public static JenkinsOrchestratorBuilder builderEnv() {
        return new JenkinsOrchestratorBuilder(Configuration.createEnv());
    }

    public static JenkinsOrchestratorBuilder builder(Configuration config) {
        return new JenkinsOrchestratorBuilder(config);
    }

    public JenkinsOrchestrator newMavenJob(String jobName, File projectPath) {
        newMavenJobConfig(jobName, projectPath);

        findElement(buttonByText("Save")).click();
        return this;
    }

    private void newMavenJobConfig(String jobName, File projectPath) {
        newJob(jobName, "hudson.maven.MavenModuleSet");
        configureScm(projectPath);
    }

    private void configureScm(File projectPath) {
        findElement(labelByText("File System")).click();
        setTextValue(findElement(By.name("fs_scm.path")), projectPath.getAbsolutePath());
    }

    private void newFreestyleJobConfig(String jobName, File projectPath) {
        newJob(jobName, "hudson.model.FreeStyleProject");
        configureScm(projectPath);
    }

    private void newJob(String jobName, String type) {
        driver.get(server.getUrl() + "/newJob");
        setTextValue(findElement(By.id("name")), jobName);
        findElement(By.xpath("//input[@type='radio' and @name='mode' and @value='" + type + "']")).click();
        findElement(By.id("ok-button")).click();
    }

    public JenkinsOrchestrator newMavenJobWithSonar(String jobName, File projectPath, String branch) {
        newMavenJobConfig(jobName, projectPath);

        activateSonarPostBuildMaven(branch);

        findElement(buttonByText("Save")).click();
        return this;
    }

    public JenkinsOrchestrator newFreestyleJobWithSonar(String jobName, File projectPath, String branch) {
        newFreestyleJobConfig(jobName, projectPath);

        findElement(buttonByText("Add build step")).click();
        findElement(By.linkText("Invoke top-level Maven targets")).click();
        setTextValue(findElement(By.name("_.targets")), "clean package");

        activateSonarPostBuildMaven(branch);

        findElement(buttonByText("Save")).click();
        return this;
    }

    public JenkinsOrchestrator newFreestyleJobWithMaven(String jobName, File projectPath, String branch,
            Orchestrator orchestrator) {
        newFreestyleJobConfig(jobName, projectPath);

        findElement(By.name("hudson-plugins-sonar-SonarBuildWrapper")).click();

        findElement(buttonByText("Add build step")).click();
        findElement(By.linkText("Invoke top-level Maven targets")).click();
        setTextValue(findElement(By.name("_.targets")), "clean package");

        findElement(buttonByText("Add build step")).click();
        findElement(By.linkText("Invoke top-level Maven targets")).click();
        setTextValue(findElement(By.name("_.targets"), 1), getMavenParams(orchestrator));

        findElement(buttonByText("Save")).click();
        return this;
    }

    public JenkinsOrchestrator newFreestyleJobWithSQScanner(String jobName, @Nullable String additionalArgs,
            File projectPath, @Nullable String sqScannerVersion, String... properties) {
        newFreestyleJobConfig(jobName, projectPath);

        findElement(buttonByText("Add build step")).click();
        findElement(By.linkText("Execute SonarQube Scanner")).click();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < properties.length / 2; i++) {
            String key = properties[2 * i];
            String value = properties[2 * i + 1];
            if (key.equals("sonar.task")) {
                setTextValue(findElement(By.name("_.task")), value);
            } else {
                builder.append(key).append("=").append(value).append("\n");
            }
        }
        setTextValue(findElement(By.name("_.properties")), builder.toString());

        if (sqScannerVersion != null) {
            select(findElement(By.name("sonar.sonarScannerName")), getSQScannerInstallName(sqScannerVersion));
        }

        if (additionalArgs != null) {
            setTextValue(findElement(By.name("_.additionalArguments")), additionalArgs);
        }

        findElement(buttonByText("Save")).click();
        return this;
    }

    public JenkinsOrchestrator newFreestyleJobWithMsBuildSQRunner(String jobName, @Nullable String additionalArgs,
            File projectPath, String projectKey, String projectName, String projectVersion,
            @Nullable String msbuildScannerVersion) {
        newFreestyleJobConfig(jobName, projectPath);

        findElement(buttonByText("Add build step")).click();
        findElement(By.linkText("SonarQube Scanner for MSBuild - Begin Analysis")).click();

        setTextValue(findElement(By.name("_.projectKey")), projectKey);
        setTextValue(findElement(By.name("_.projectName")), projectName);
        setTextValue(findElement(By.name("_.projectVersion")), projectVersion);

        if (msbuildScannerVersion != null) {
            select(findElement(By.name("msBuildScannerInstallationName")),
                    getScannerMSBuildInstallName(msbuildScannerVersion));
        }

        if (additionalArgs != null) {
            setTextValue(findElement(By.name("_.additionalArguments")), additionalArgs);
        }

        findElement(buttonByText("Add build step")).click();
        findElement(By.linkText("SonarQube Scanner for MSBuild - End Analysis")).click();

        findElement(buttonByText("Save")).click();
        return this;
    }

    private void activateSonarPostBuildMaven(String branch) {
        WebElement addPostBuildButton = findElement(buttonByText("Add post-build action"));
        addPostBuildButton.click();
        findElement(By.linkText("SonarQube analysis with Maven")).click();
        // Here we need to wait for the Sonar step to be really activated
        WebElement sonarPublisher = findElement(
                By.xpath("//div[@descriptorid='hudson.plugins.sonar.SonarPublisher']"));
        if (StringUtils.isNotBlank(branch)) {
            sonarPublisher.findElement(buttonByText("Advanced...")).click();
            setTextValue(sonarPublisher.findElement(By.name("sonar.branch")), branch);
        }
    }

    public String getSonarUrlOnJob(String jobName) {
        driver.get(server.getUrl() + "/job/" + jobName);
        return findElement(By.linkText("SonarQube")).getAttribute("href");
    }

    public JenkinsOrchestrator configureMavenInstallation() {
        if (config.fileSystem().mavenHome() == null) {
            throw new RuntimeException("Please configure MAVEN_HOME");
        }
        driver.get(server.getUrl() + "/configure");

        WebElement addMavenButton = findElement(buttonByText("Add Maven"));
        addMavenButton.click();
        setTextValue(findElement(By.name("_.name")), "Maven");
        findElement(By.name("hudson-tools-InstallSourceProperty")).click();
        setTextValue(findElement(By.name("_.home")), config.fileSystem().mavenHome().getAbsolutePath());
        findElement(buttonByText("Save")).click();

        return this;
    }

    /**
     * Scroll so that element is centered to make element visible even with Jenkins bottom/top floating bars
     */
    public WebElement scrollTo(WebElement e) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript(
                "const element = arguments[0]; const elementRect = element.getBoundingClientRect(); const absoluteElementTop = elementRect.top + window.pageYOffset; const top = absoluteElementTop - (window.innerHeight / 2); window.scrollTo(0, top);",
                e);
        // Give the time for the floating bar to move at the bottom
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        return e;
    }

    public JenkinsOrchestrator configureSQScannerInstallation(String version, int index) {
        driver.get(server.getUrl() + "/configure");

        SonarScannerInstaller installer = new SonarScannerInstaller(config.fileSystem());
        File runnerScript = installer.install(Version.create(version), config.fileSystem().workspace(), true);

        if (index > 0) {
            findElement(buttonByText("SonarQube Scanner installations...")).click();
        }

        findElement(buttonByText("Add SonarQube Scanner")).click();
        setTextValue(findElement(By.name("_.name"), index), getSQScannerInstallName(version));
        findElement(By.name("hudson-tools-InstallSourceProperty"), index).click();
        WebElement homeDir = findElement(By.name("_.home"), index);
        setTextValue(homeDir, runnerScript.getParentFile().getParentFile().getAbsolutePath());
        findElement(buttonByText("Save")).click();

        return this;
    }

    private String getSQScannerInstallName(String version) {
        return "SonarQube Scanner " + version;
    }

    public JenkinsOrchestrator configureMsBuildSQScanner_installation(String version, int index) {
        driver.get(server.getUrl() + "/configure");

        if (index > 0) {
            findElement(buttonByText("SonarQube Scanner for MSBuild installations...")).click();
        }

        findElement(buttonByText("Add SonarQube Scanner for MSBuild")).click();
        setTextValue(findElement(By.name("_.name"), index), getScannerMSBuildInstallName(version));
        select(findElement(By.name("_.id"), index), version);
        findElement(buttonByText("Save")).click();

        return this;
    }

    private String getScannerMSBuildInstallName(String version) {
        return "Scanner for MSBuild " + version;
    }

    public JenkinsOrchestrator enableInjectionVars(boolean enable) {
        driver.get(server.getUrl() + "/configure");

        WebElement checkbox = findElement(By.name("enableBuildWrapper"));
        if (checkbox.isSelected() != enable) {
            checkbox.click();
        }
        findElement(buttonByText("Save")).click();
        return this;
    }

    public JenkinsOrchestrator configureSonarInstallation(Orchestrator orchestrator) {
        Version serverVersion = orchestrator.getServer().version();

        driver.get(server.getUrl() + "/configure");
        findElement(buttonByText("Add SonarQube")).click();

        setTextValue(findElement(By.name("sonar.name")), "SonarQube");
        setTextValue(findElement(By.name("sonar.serverUrl")), orchestrator.getServer().getUrl());
        findElement(buttonByTextAfterElementByXpath("Advanced...", "//.[@name='sonar.name']")).click();

        if (serverVersion.isGreaterThanOrEquals("5.3")) {
            String token = generateToken(orchestrator);
            select(findElement(By.className("sonar-server-version")), "5.3");
            setTextValue(findElement(By.name("sonar.serverAuthenticationToken")), token);

            assertThat(findElement(By.name("sonar.sonarLogin")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.sonarPassword")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseUrl")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseLogin")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databasePassword")).isEnabled()).isFalse();

        } else if (serverVersion.isGreaterThan("5.2")) {
            select(findElement(By.className("sonar-server-version")), "5.2");
            setTextValue(findElement(By.name("sonar.sonarLogin")), Server.ADMIN_LOGIN);
            setTextValue(findElement(By.name("sonar.sonarPassword")), Server.ADMIN_PASSWORD);

            assertThat(findElement(By.name("sonar.serverAuthenticationToken")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseUrl")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseLogin")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databasePassword")).isEnabled()).isFalse();
        } else {
            select(findElement(By.className("sonar-server-version")), "5.1");
            setTextValue(findElement(By.name("sonar.sonarLogin")), Server.ADMIN_LOGIN);
            setTextValue(findElement(By.name("sonar.sonarPassword")), Server.ADMIN_PASSWORD);
            setTextValue(findElement(By.name("sonar.databaseUrl")),
                    orchestrator.getDatabase().getSonarProperties().get("sonar.jdbc.url"));
            setTextValue(findElement(By.name("sonar.databaseLogin")),
                    orchestrator.getDatabase().getSonarProperties().get("sonar.jdbc.username"));
            setTextValue(findElement(By.name("sonar.databasePassword")),
                    orchestrator.getDatabase().getSonarProperties().get("sonar.jdbc.password"));

            assertThat(findElement(By.name("sonar.serverAuthenticationToken")).isEnabled()).isFalse();
        }

        findElement(buttonByText("Save")).click();

        return this;
    }

    public void configureDefaultQG(Orchestrator orchestrator) {
        QualityGates qualityGates = orchestrator.getServer().adminWsClient().qualityGateClient().list();
        assertThat(qualityGates.qualityGates().size()).isGreaterThan(0);

        long id = qualityGates.qualityGates().iterator().next().id();
        orchestrator.getServer().adminWsClient().qualityGateClient().setDefault(id);
        System.out.println("Set default QG: " + id);
    }

    public void checkSavedSonarInstallation(Orchestrator orchestrator) {
        Version serverVersion = orchestrator.getServer().version();

        driver.get(server.getUrl() + "/configure");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        assertThat(findElement(By.name("sonar.name")).getAttribute("value")).isEqualTo("SonarQube");
        assertThat(findElement(By.name("sonar.serverUrl")).getAttribute("value"))
                .isEqualTo(orchestrator.getServer().getUrl());
        findElement(buttonByTextAfterElementByXpath("Advanced...", "//.[@name='sonar.name']")).click();

        if (serverVersion.isGreaterThanOrEquals("5.3")) {
            assertThat(findElement(By.name("sonar.serverAuthenticationToken")).getAttribute("value")).isNotEmpty();
            assertThat(findElement(By.name("sonar.sonarLogin")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.sonarPassword")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseUrl")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseLogin")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databasePassword")).isEnabled()).isFalse();

        } else if (serverVersion.isGreaterThan("5.2")) {
            assertThat(findElement(By.name("sonar.sonarLogin")).getAttribute("value"))
                    .isEqualTo(Server.ADMIN_LOGIN);
            assertThat(findElement(By.name("sonar.sonarPassword")).getAttribute("value"))
                    .isEqualTo(Server.ADMIN_PASSWORD);
            assertThat(findElement(By.name("sonar.serverAuthenticationToken")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseUrl")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databaseLogin")).isEnabled()).isFalse();
            assertThat(findElement(By.name("sonar.databasePassword")).isEnabled()).isFalse();
        } else {
            assertThat(findElement(By.name("sonar.sonarLogin")).getAttribute("value"))
                    .isEqualTo(Server.ADMIN_LOGIN);
            assertThat(findElement(By.name("sonar.sonarPassword")).getAttribute("value"))
                    .isEqualTo(Server.ADMIN_PASSWORD);
            assertThat(findElement(By.name("sonar.databaseUrl")).getAttribute("value"))
                    .isEqualTo(orchestrator.getDatabase().getSonarProperties().get("sonar.jdbc.url"));
            assertThat(findElement(By.name("sonar.databaseLogin")).getAttribute("value"))
                    .isEqualTo(orchestrator.getDatabase().getSonarProperties().get("sonar.jdbc.username"));
            assertThat(findElement(By.name("sonar.databasePassword")).getAttribute("value"))
                    .isEqualTo(orchestrator.getDatabase().getSonarProperties().get("sonar.jdbc.password"));
            assertThat(findElement(By.name("sonar.serverAuthenticationToken")).isEnabled()).isFalse();
        }
    }

    public String generateToken(Orchestrator orchestrator) {
        String json = orchestrator.getServer().adminWsClient().post("api/user_tokens/generate", "name",
                "token" + tokenCounter++);
        Map response = (Map) JSONValue.parse(json);
        return (String) response.get("token");
    }

    public JenkinsOrchestrator installPlugin(Location hpi) {
        File hpiFile = config.fileSystem().copyToDirectory(hpi, server.getHome().getParentFile());
        if (hpiFile == null || !hpiFile.exists()) {
            throw new IllegalStateException("Can not find the plugin " + hpi);
        }
        cli.execute("install-plugin", hpiFile.getAbsolutePath(), "-deploy");
        return this;
    }

    public JenkinsOrchestrator disablePlugin(String name) throws IOException {
        File f = new File(new File(server.getHome(), "plugins"), name + ".hpi.disabled");
        f.createNewFile();
        return this;
    }

    public JenkinsOrchestrator enablePlugin(String name) {
        File f = new File(new File(server.getHome(), "plugins"), name + ".hpi.disabled");
        FileUtils.deleteQuietly(f);
        return this;
    }

    public BuildResult executeJob(String jobName) {
        BuildResult result = executeJobQuietly(jobName);
        if (result.getStatus() != 0) {
            throw new RuntimeException("Error during build of " + jobName + "\n" + result.getLogs());
        }
        return result;
    }

    public BuildResult executeJobQuietly(String jobName) {
        BuildResult result = new BuildResult();
        result.setStatus(cli.execute("build", jobName, "-s"));
        WriterOutputStream out = new WriterOutputStream(result.getLogsWriter());
        cli.execute(Arrays.asList("console", jobName), System.in, out, out);
        return result;
    }

    private String getMavenParams(Orchestrator orchestrator) {
        Version serverVersion = orchestrator.getServer().version();

        if (serverVersion.isGreaterThanOrEquals("5.3")) {
            return "$SONAR_MAVEN_GOAL -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_AUTH_TOKEN";
        } else {
            return "$SONAR_MAVEN_GOAL -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -Dsonar.password=$SONAR_PASSWORD"
                    + " -Dsonar.jdbc.url=$SONAR_JDBC_URL -Dsonar.jdbc.username=$SONAR_JDBC_USERNAME -Dsonar.jdbc.password=$SONAR_JDBC_PASSWORD";
        }
    }

    private By buttonByText(String text) {
        return By.xpath(".//button[normalize-space(.) = '" + text + "']");
    }

    private By buttonByTextAfterElementByXpath(String text, String xpath) {
        return By.xpath(xpath + "/following::button[normalize-space(.) = '" + text + "']");
    }

    private By labelByText(String text) {
        return By.xpath("//label[normalize-space(.) = '" + text + "']");
    }

    public WebDriver getDriver() {
        return driver;
    }

    public WebElement findElement(By by) {
        try {
            return scrollTo(driver.findElement(by));
        } catch (NoSuchElementException e) {
            System.err.println("Element not found. Save screenshot to: target/no_such_element.png");
            takeScreenshot(new File("target/no_such_element.png"));
            throw e;
        }
    }

    public void assertNoElement(By by) {
        List<WebElement> elements = driver.findElements(by);
        if (!elements.isEmpty()) {
            System.err.println("Not expecting finding element, but found " + elements.size()
                    + ". Save screenshot to: target/no_such_element.png");
            takeScreenshot(new File("target/no_such_element.png"));
        }
    }

    public WebElement findElement(By by, int index) {
        try {
            List<WebElement> elms = driver.findElements(by);
            return scrollTo(elms.get(index));
        } catch (NoSuchElementException e) {
            System.err.println("Element not found. Save screenshot to: target/no_such_element.png");
            takeScreenshot(new File("target/no_such_element.png"));
            throw e;
        }
    }

    public void takeScreenshot(File toFile) {
        File tmp = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        try {
            FileUtils.copyFile(tmp, toFile);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Used to fix false positive about strange chars added by sendKey
     * http://code.google.com/p/selenium/issues/detail?id=4446
     * Update: it is no more necessary as I have disabled native events but I keep it just in case
     */
    public void setTextValue(final WebElement element, final String text) {
        (new WebDriverWait(driver, 10)).until(new Function<WebDriver, Boolean>() {
            @Override
            public Boolean apply(WebDriver input) {
                element.clear();
                element.sendKeys(text);
                return element.getAttribute("value").equals(text);
            }
        });
    }

    public void assertNoSonarPublisher(String jobName, File projectPath) {
        newFreestyleJobConfig(jobName, projectPath);

        WebElement addPostBuildButton = findElement(buttonByText("Add post-build action"));
        scrollTo(addPostBuildButton);
        addPostBuildButton.click();
        findElement(By.linkText("SonarQube analysis with Maven")).click();
        assertNoElement(By.xpath("//div[@descriptorid='hudson.plugins.sonar.SonarPublisher']"));
        findElement(buttonByText("Save")).click();
    }

    public void select(WebElement element, String optionValue) {
        Select select = new Select(element);
        select.selectByValue(optionValue);
    }

    public void assertQGOnProjectPage(String jobName) {
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        driver.get(server.getUrl() + "/job/" + jobName);
        WebElement table = findElement(By.className("sonar-projects"));
        WebElement qg = findElement(By.className("sonar-qg"));
        assertThat(table).isNotNull();
        assertThat(qg).isNotNull();
    }
}