/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2007, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ package net.sourceforge.cruisecontrol.dashboard.seleniumtests; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import org.apache.commons.lang.StringUtils; import org.apache.commons.io.FileUtils; import org.cyberneko.html.parsers.DOMParser; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import junit.framework.TestCase; import net.sourceforge.cruisecontrol.dashboard.testhelpers.DataUtils; import net.sourceforge.cruisecontrol.dashboard.testhelpers.jmxstub.BuildLoopQueryServiceStub; import net.sourceforge.cruisecontrol.util.OSEnvironment; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium; public abstract class SeleniumTestCase extends TestCase { private String originalContent; private File configXmlOfWebApp = null; protected Selenium user; private String browser = ""; private boolean didChangeConfigFile; public SeleniumTestCase() { String browserPath = new OSEnvironment().getVariable("BROWSER_PATH"); if (StringUtils.isEmpty(browserPath)) { throw new RuntimeException("You must define browser " + "path using env variable BROWSER_PATH"); } if (StringUtils.containsIgnoreCase(browserPath, "firefox")) { browser = "*firefox"; } else if (StringUtils.containsIgnoreCase(browserPath, "iexplore")) { browser = "*iexplore"; } else { throw new RuntimeException("BROWSER_PATH should either point to firefox or IE"); } } public final void setUp() throws Exception { user = new DefaultSelenium("localhost", 4444, browser, "http://localhost:9090"); user.start(); DataUtils.cloneCCHome(); didChangeConfigFile = false; doSetUp(); } public final void tearDown() throws Exception { doTearDown(); rollbackConfigFile(); user.stop(); } protected void doSetUp() throws Exception { } protected void doTearDown() throws Exception { } protected void willChangeConfigFile() throws Exception { configXmlOfWebApp = DataUtils.getConfigXmlOfWebApp(); originalContent = FileUtils.readFileToString(configXmlOfWebApp, "UTF-8"); } protected Document getHtmlDom() throws Exception { DOMParser parser = new DOMParser(); parser.parse(new InputSource(new ByteArrayInputStream(user.getHtmlSource().getBytes()))); return parser.getDocument(); } protected void hasClassName(String id, String className) throws Exception { String classNames = getClassName(id); assertTrue(StringUtils.contains(classNames, className)); } protected String getAttribute(String id, String attributeName) throws Exception { Element element = getHtmlDom().getElementById(id); return element.getAttribute(attributeName); } protected void hasNoClassName(String id, String className) throws Exception { String classNames = getClassName(id); assertFalse(StringUtils.contains(classNames, className)); } private String getClassName(String id) throws Exception { Document htmlDom = getHtmlDom(); String classNames = htmlDom.getElementById(id).getAttribute("class"); return classNames; } protected static final int AJAX_DURATION = 5; protected static final int FORCE_BUILD_DURATION = AJAX_DURATION * 3; protected static final int BUILD_DURATION = AJAX_DURATION * (BuildLoopQueryServiceStub.BUILD_TIMES.intValue() * 2); protected static final int LONG_TIME = 60; protected static final String BUILDING_STARTED = "Elapsed"; protected void forceBuildByClick(String projectName) { user.click(projectName + "_forcebuild"); } protected void forceBuildByRequest(String projectName) { user.open("/dashboard/forcebuild.ajax?projectName=" + projectName); user.goBack(); } protected void textShouldAppearInCertainTime(String text, int seconds) { String textPresent = "selenium.isTextPresent(\"" + text + "\")"; user.waitForCondition(textPresent, "" + seconds * 1000); } protected void textShouldDisappearInCertainTime(String text, int seconds) { String textPresent = "!selenium.isTextPresent(\"" + text + "\")"; user.waitForCondition(textPresent, "" + seconds * 1000); } protected void waitingForElementDisappear(String id, int seconds) { String isElementHidden = "!selenium.isVisible(\"" + id + "\")"; user.waitForCondition(isElementHidden, "" + seconds * 1000); } protected void elementShouldAppearInCertainTime(String id, int seconds) { String isElementAppear = "selenium.isVisible(\"" + id + "\")"; user.waitForCondition(isElementAppear, "" + seconds * 1000); } protected void textShouldPresent(String text) { assertTrue(user.isTextPresent(text)); } protected void textShouldNOTPresent(String text) { assertFalse(user.isTextPresent(text)); } protected void textShouldBeContainedInHtmlSource(String text) { assertTrue(user.getHtmlSource().toLowerCase(), StringUtils.contains(user.getHtmlSource() .toLowerCase(), text)); } protected void elementShouldPresent(String elementId) { assertTrue(user.isElementPresent(elementId)); } protected void elementShouldNotBePresent(String elementId) { assertFalse(user.isElementPresent(elementId)); } protected void elementShouldBeVisible(String elementId) { assertTrue(user.isVisible(elementId)); } protected void elementShouldNOTBeVisible(String elementId) { assertFalse(user.isVisible(elementId)); } protected void elementShouldContainText(String elementId, String text) { assertEquals(text, user.getText(elementId)); } protected void openDashboardPage() { openAndWaiting("/dashboard/tab/dashboard"); } protected void openBuildsPage() { openAndWaiting("/dashboard/tab/builds"); } protected void openAllBuilsPage(String projectName) { openAndWaiting("/dashboard/project/list/all/" + projectName); } protected void openAllSuccessfulBuilsPage(String projectName) { openAndWaiting("/dashboard/project/list/all/" + projectName); } protected void openBuildDetailPage(String projectName, String logFile) { openAndWaiting("/dashboard/tab/build/detail/" + projectName + "/" + logFile); } protected void openBuildDetailPageDirectly(String projectName) { openAndWaiting("/dashboard/tab/build/detail/" + projectName); } protected void clickToOpenBuildDetailPageOf(String projectName) throws Exception { clickAndWait(projectName + "_build_detail"); } protected void clickConfigPanelOf(String projectName) throws Exception { user.click(projectName + "_config_panel"); } protected void clickLinkWithTextAndWait(String text) throws Exception { clickElementWithTextAndWait("a", text); } protected void clickElementWithText(String tag, String text) { user.click("//" + tag + "[text()='" + text + "']"); } protected void clickElementWithTextAndWait(String tag, String text) throws Exception { clickElementWithText(tag, text); Thread.sleep(1000); } protected void clickAndWait(String elementSelector) throws Exception { user.click(elementSelector); user.waitForPageToLoad("50000"); } protected void waitUntilStatisticsChange(String textBefore, String status, int seconds) throws Exception { int elapsed = 0; while (elapsed < seconds * 1000) { if (!StringUtils.equalsIgnoreCase(textBefore, user.getText("statistics_" + status))) { return; } Thread.sleep(500); elapsed += 500; } throw new Exception("Time out after " + seconds); } private void openAndWaiting(String url) { user.open(url); user.waitForPageToLoad("20000"); } protected boolean waitForElementPresentWithRefresh(String id, int seconds) throws Exception { int total = seconds * 1000; int elapsed = 0; while (elapsed < total) { if (user.isElementPresent(id)) { return true; } Thread.sleep(100); elapsed += 100; user.refresh(); } return false; } protected void addProjectToConfigFile(String projectName) throws IOException { if (configXmlOfWebApp == null) { throw new RuntimeException("Must call willChangeConfigFile() in doSetUp()"); } didChangeConfigFile = true; String newConfig = StringUtils.replace(originalContent, "</cruisecontrol>", xmlSnippet(projectName) + "</cruisecontrol>"); FileUtils.writeStringToFile(configXmlOfWebApp, newConfig); } protected void rollbackConfigFile() throws IOException { if (didChangeConfigFile) { FileUtils.writeStringToFile(configXmlOfWebApp, originalContent); } } private String xmlSnippet(String projectName) { return "<project name=\"" + projectName + "\">" + "<listeners>" + "<currentbuildstatuslistener file=\"logs/${project.name}/status.txt\"/>" + "</listeners>" + "<bootstrappers>" + "<svnbootstrapper localWorkingCopy=\"projects/${project.name}\"/>" + "</bootstrappers>" + "<modificationset>" + "<svn localWorkingCopy=\"projects/${project.name}\"/>" + "</modificationset>" + "<schedule>" + "<ant buildfile=\"projects/${project.name}/build.xml\"/>" + "</schedule>" + "<publishers>" + "<onsuccess>" + "<artifactspublisher dest=\"artifacts/${project.name}\" file=\"projects/${project.name}/target/${project.name}.jar\"/>" + "</onsuccess>" + "</publishers>" + "</project>"; } }