/****************************************************************************
* Copyright 2008-2011 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Initial Contributors:
* Håkan Råberg
* Manish Chakravarty
* Pavan K S
***************************************************************************/
/*
* Copyright 2004 ThoughtWorks, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.thoughtworks.selenium;
import java.io.File;
import java.lang.reflect.Proxy;
import java.net.BindException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.eclipse.swt.widgets.Display;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.webapp.WebAppContext;
import com.thoughtworks.krypton.driver.web.browser.BrowserFamily;
import com.thoughtworks.krypton.driver.web.browser.Decorators.SWTThreadingDecorator;
import com.thoughtworks.krypton.driver.web.selenium.TwistSelenium;
/**
* Provides a JUnit TestCase base class that implements some handy functionality
* for Selenium testing (you are <i>not</i> required to extend this class).
*
* <p>
* This class adds a number of "verify" commands, which are like "assert"
* commands, but they don't stop the test when they fail. Instead, verification
* errors are all thrown at once during tearDown.
* </p>
*
* @author Nelson Sproul (nsproul@bea.com) Mar 13-06
*/
public class SeleneseTestCase extends TestCase {
protected static final int WEBSERVER_PORT = 9999;
private static final boolean THIS_IS_WINDOWS = File.pathSeparator.equals(";");
private boolean captureScreetShotOnFailure = false;
/** Use this object to run all of your selenium tests */
protected static DefaultSelenium suiteSelenium;
protected Selenium selenium;
private static int port = WEBSERVER_PORT;
static {
try {
startJetty();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Server server;
public SeleneseTestCase() {
super();
}
public SeleneseTestCase(String name) {
super(name);
}
/**
* Calls this.setUp(null)
*
* @see #setUp(String)
*/
public void setUp() throws Exception {
super.setUp();
this.setUp(null);
}
public String getName() {
return super.getName() + " " + BrowserFamily.fromSystemProperty() + " " + System.getProperty("os.name");
}
private Executor executor = Executors.newSingleThreadExecutor();
/**
* Runs the bare test sequence, capturing a screenshot if a test fails
*
* @exception Throwable
* if any exception is thrown
*/
// @Override
public void runBare() throws Throwable {
runBareWithSecondThread();
// runBareWithOneThread();
}
private void runBareWithSecondThread() throws Throwable {
final boolean[] done = new boolean[1];
final Throwable[] throwable = new Throwable[1];
final Display display = Display.getDefault();
executor.execute(new Runnable() {
public void run() {
try {
runBareWithOneThread();
} catch (Throwable t) {
throwable[0] = t;
} finally {
done[0] = true;
}
}
});
while ((!display.isDisposed())) {
try {
display.readAndDispatch();
} catch (Exception whoCares) {
}
if (done[0]) {
break;
}
}
if (throwable[0] != null) {
throw throwable[0];
}
}
private static void startJetty() throws Exception {
try {
server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
port = findAvailablePort();
connector.setPort(port);
server.addConnector(connector);
WebAppContext context = new WebAppContext();
context.setContextPath("/selenium-server");
File file = new File(SeleneseTestCase.class.getClassLoader().getResource("tests").toURI());
context.setResourceBase(file.getParent());
server.addHandler(context);
server.start();
} catch (BindException alreadyRunning) {
}
}
private static int findAvailablePort() {
return WEBSERVER_PORT + BrowserFamily.fromSystemProperty().ordinal();
}
/**
* Calls this.setUp with the specified url and a default browser. On
* Windows, the default browser is *iexplore; otherwise, the default browser
* is *firefox.
*
* @see #setUp(String, String)
* @param url
* the baseUrl to use for your Selenium tests
* @throws Exception
*
*/
public void setUp(String url) throws Exception {
if (THIS_IS_WINDOWS) {
setUp(url, "*iexplore");
} else {
setUp(url, "*firefox");
}
}
/**
* Creates a new DefaultSelenium object and starts it using the specified
* baseUrl and browser string
*
* @param url
* the baseUrl for your tests
* @param browserString
* the browser to use, e.g. *firefox
* @throws Exception
*/
public void setUp(String url, String browserString) throws Exception {
super.setUp();
int port = getWebServerPort();
if (url == null) {
url = "http://localhost:" + port;
}
if (suiteSelenium == null) {
suiteSelenium = new DefaultSelenium("", getWebServerPort(), "", url);
suiteSelenium.start();
suiteSelenium.setContext(this.getClass().getSimpleName() + "." + getName());
}
getTwistSelenium().setBrowserUrl(url);
selenium = suiteSelenium;
selenium.selectFrame("relative=top");
}
@SuppressWarnings("unchecked")
private TwistSelenium getTwistSelenium() {
Selenium underlyingSelenium = suiteSelenium.getUnderlyingSelenium();
if (underlyingSelenium instanceof TwistSelenium) {
return (TwistSelenium) underlyingSelenium;
}
SWTThreadingDecorator<Selenium> handler = (SWTThreadingDecorator<Selenium>) Proxy.getInvocationHandler(underlyingSelenium);
return ((TwistSelenium) handler.getInstance());
}
protected int getWebServerPort() {
return port;
}
/** Like assertTrue, but fails at the end of the test (during tearDown) */
public void verifyTrue(boolean b) {
assertTrue(b);
}
/** Like assertFalse, but fails at the end of the test (during tearDown) */
public void verifyFalse(boolean b) {
assertFalse(b);
}
/** Returns the body text of the current page */
public String getText() {
return selenium.getText("document.body");
}
/** Like assertEquals, but fails at the end of the test (during tearDown) */
public void verifyEquals(Object s1, Object s2) {
assertEquals(s1, s2);
}
/** Like assertEquals, but fails at the end of the test (during tearDown) */
public void verifyEquals(boolean s1, boolean s2) {
assertEquals(new Boolean(s1), new Boolean(s2));
}
/**
* Like JUn it's Assert.assertEquals, but knows how to compare string arrays
*/
public static void assertEquals(Object s1, Object s2) {
if (s1 instanceof String && s2 instanceof String) {
assertEquals((String) s1, (String) s2);
} else if (s1 instanceof String && s2 instanceof String[]) {
assertEquals((String) s1, (String[]) s2);
} else if (s1 instanceof String && s2 instanceof Number) {
assertEquals((String) s1, ((Number) s2).toString());
} else {
if (s1 instanceof String[] && s2 instanceof String[]) {
String[] sa1 = (String[]) s1;
String[] sa2 = (String[]) s2;
if (sa1.length != sa2.length) {
throw new AssertionFailedError("Expected " + sa1 + " but saw " + sa2);
}
for (int j = 0; j < sa1.length; j++) {
Assert.assertEquals(sa1[j], sa2[j]);
}
}
}
}
/**
* Like JUnit's Assert.assertEquals, but handles "regexp:" strings like HTML
* Selenese
*/
public static void assertEquals(String s1, String s2) {
assertTrue("Expected \"" + s1 + "\" but saw \"" + s2 + "\" instead", seleniumEquals(s1, s2));
}
/**
* Like JUnit's Assert.assertEquals, but joins the string array with commas,
* and handles "regexp:" strings like HTML Selenese
*/
public static void assertEquals(String s1, String[] s2) {
assertEquals(s1, stringArrayToSimpleString(s2));
}
/**
* Compares two strings, but handles "regexp:" strings like HTML Selenese
*
* @param expectedPattern
* @param actual
* @return true if actual matches the expectedPattern, or false otherwise
*/
public static boolean seleniumEquals(String expectedPattern, String actual) {
if (actual.startsWith("regexp:") || actual.startsWith("regex:") || actual.startsWith("regexpi:") || actual.startsWith("regexi:")) {
// swap 'em
String tmp = actual;
actual = expectedPattern;
expectedPattern = tmp;
}
Boolean b;
b = handleRegex("regexp:", expectedPattern, actual, 0);
if (b != null) {
return b.booleanValue();
}
b = handleRegex("regex:", expectedPattern, actual, 0);
if (b != null) {
return b.booleanValue();
}
b = handleRegex("regexpi:", expectedPattern, actual, Pattern.CASE_INSENSITIVE);
if (b != null) {
return b.booleanValue();
}
b = handleRegex("regexi:", expectedPattern, actual, Pattern.CASE_INSENSITIVE);
if (b != null) {
return b.booleanValue();
}
if (expectedPattern.startsWith("exact:")) {
String expectedExact = expectedPattern.replaceFirst("exact:", "");
if (!expectedExact.equals(actual)) {
System.out.println("expected " + actual + " to match " + expectedPattern);
return false;
}
return true;
}
String expectedGlob = expectedPattern.replaceFirst("glob:", "");
expectedGlob = expectedGlob.replaceAll("([\\]\\[\\\\{\\}$\\(\\)\\|\\^\\+.])", "\\\\$1");
expectedGlob = expectedGlob.replaceAll("\\*", ".*");
expectedGlob = expectedGlob.replaceAll("\\?", ".");
if (!Pattern.compile(expectedGlob, Pattern.DOTALL).matcher(actual).matches()) {
System.out.println("expected \"" + actual + "\" to match glob \"" + expectedPattern
+ "\" (had transformed the glob into regexp \"" + expectedGlob + "\"");
return false;
}
return true;
}
private static Boolean handleRegex(String prefix, String expectedPattern, String actual, int flags) {
if (expectedPattern.startsWith(prefix)) {
String expectedRegEx = expectedPattern.replaceFirst(prefix, ".*") + ".*";
Pattern p = Pattern.compile(expectedRegEx, flags);
if (!p.matcher(actual).matches()) {
System.out.println("expected " + actual + " to match regexp " + expectedPattern);
return Boolean.FALSE;
}
return Boolean.TRUE;
}
return null;
}
/**
* Compares two objects, but handles "regexp:" strings like HTML Selenese
*
* @see #seleniumEquals(String, String)
* @return true if actual matches the expectedPattern, or false otherwise
*/
public static boolean seleniumEquals(Object expected, Object actual) {
if (expected instanceof String && actual instanceof String) {
return seleniumEquals((String) expected, (String) actual);
}
return expected.equals(actual);
}
/** Asserts that two string arrays have identical string contents */
public static void assertEquals(String[] s1, String[] s2) {
String comparisonDumpIfNotEqual = verifyEqualsAndReturnComparisonDumpIfNot(s1, s2);
if (comparisonDumpIfNotEqual != null) {
throw new AssertionFailedError(comparisonDumpIfNotEqual);
}
}
/**
* Asserts that two string arrays have identical string contents (fails at
* the end of the test, during tearDown)
*/
public void verifyEquals(String[] s1, String[] s2) {
String comparisonDumpIfNotEqual = verifyEqualsAndReturnComparisonDumpIfNot(s1, s2);
if (comparisonDumpIfNotEqual != null) {
throw new AssertionError(comparisonDumpIfNotEqual);
}
}
private static String verifyEqualsAndReturnComparisonDumpIfNot(String[] s1, String[] s2) {
boolean misMatch = false;
if (s1.length != s2.length) {
misMatch = true;
}
for (int j = 0; j < s1.length; j++) {
if (!seleniumEquals(s1[j], s2[j])) {
misMatch = true;
break;
}
}
if (misMatch) {
return "Expected " + stringArrayToString(s1) + " but saw " + stringArrayToString(s2);
}
return null;
}
private static String stringArrayToString(String[] sa) {
StringBuffer sb = new StringBuffer("{");
for (int j = 0; j < sa.length; j++) {
sb.append(" ").append("\"").append(sa[j]).append("\"");
}
sb.append(" }");
return sb.toString();
}
private static String stringArrayToSimpleString(String[] sa) {
StringBuffer sb = new StringBuffer();
for (int j = 0; j < sa.length; j++) {
sb.append(sa[j]);
if (j < sa.length - 1) {
sb.append(',');
}
}
return sb.toString();
}
/** Like assertNotEquals, but fails at the end of the test (during tearDown) */
public void verifyNotEquals(Object s1, Object s2) {
assertNotEquals(s1, s2);
}
/** Like assertNotEquals, but fails at the end of the test (during tearDown) */
public void verifyNotEquals(boolean s1, boolean s2) {
assertNotEquals(new Boolean(s1), new Boolean(s2));
}
/** Asserts that two objects are not the same (compares using .equals()) */
public static void assertNotEquals(Object obj1, Object obj2) {
if (obj1.equals(obj2)) {
fail("did not expect values to be equal (" + obj1.toString() + ")");
}
}
/** Asserts that two booleans are not the same */
public static void assertNotEquals(boolean b1, boolean b2) {
assertNotEquals(new Boolean(b1), new Boolean(b2));
}
/** Sleeps for the specified number of milliseconds */
public void pause(int millisecs) {
// try {
// Thread.sleep(millisecs);
// } catch (InterruptedException e) {
// }
}
/**
* Asserts that there were no verification errors during the current test,
* failing immediately if any are found
*/
public void checkForVerificationErrors() {
clearVerificationErrors();
}
/** Clears out the list of verification errors */
public void clearVerificationErrors() {
}
/** checks for verification errors and stops the browser */
public void tearDown() throws Exception {
try {
checkForVerificationErrors();
} finally {
getTwistSelenium().closeAllDialogs();
// selenium.stop();
}
}
protected boolean isCaptureScreetShotOnFailure() {
return captureScreetShotOnFailure;
}
protected void setCaptureScreetShotOnFailure(boolean captureScreetShotOnFailure) {
this.captureScreetShotOnFailure = captureScreetShotOnFailure;
}
private void runBareWithOneThread() throws Throwable, Exception {
if (!isCaptureScreetShotOnFailure()) {
// startJetty();
SeleneseTestCase.super.runBare();
return;
}
setUp();
try {
runTest();
} catch (Throwable t) {
if (selenium != null) {
String filename = getName() + ".png";
try {
selenium.captureScreenshot(filename);
System.err.println("Saved screenshot " + filename);
} catch (Exception e) {
System.err.println("Couldn't save screenshot " + filename + ": " + e.getMessage());
e.printStackTrace();
}
throw t;
}
} finally {
tearDown();
}
}
}