JWebUnit for functional testing

1,413 views
Skip to first unread message

Brian Nesbitt

unread,
Aug 30, 2011, 1:40:17 PM8/30/11
to play-fr...@googlegroups.com
Sort of a follow up to my post from yesterday: https://groups.google.com/forum/#!topic/play-framework/cqnERcgokG0
I was surprised that when I did a search for JWebUnit on this group I didn't get a single hit?!?

Its basically a wrapper for HtmlUnit with a very complete list of helper functions.
Officialy its "JWebUnit is a Java-based testing framework for web applications. It wraps existing testing frameworks such as HtmlUnit and Selenium with a unified, simple testing interface. The simple navigation methods and ready-to-use assertions allow for more rapid test creation than using only JUnit or HtmlUnit."

You can see all of the helper functions it supplies here:

And a quickstart guide here:

Reusing the HtmlUnit test from yesterday, I rewrote it using JWebUnit as you can see below.  Having the IDE auto-complete is very nice.

@Test
public void testPlayHomepage() throws IOException
{
   WebClient webClient = new WebClient();
   HtmlPage page = webClient.getPage("http://www.playframework.com");
   assertEquals("Play framework - Home", page.getTitleText());

   String pageAsXml = page.asXml();
   assertTrue(pageAsXml.contains("<div id=\"introduction\">"));

   String pageAsText = page.asText();
   assertTrue(pageAsText.contains("The Play framework makes it easier to build Web applications with Java and Scala"));

   webClient.closeAllWindows();
}

@Test
public void testPlayHomepageWithJWebUnit()
{
   setBaseUrl("http://www.playframework.com");
   beginAt("/");
   assertTitleEquals("Play framework - Home");

   assertTextInElement("introduction", "The Play framework makes it easier to build Web applications with Java and Scala");
}

You get simpler and better coverage in the 2nd example because you are ensuring the text is inside the element with the specific id.
The one call to "assertTextInElement" ensures the element exists and that the text is in that element.

As I mentioned yesterday you need to put "%test.play.pool=2" in your config so there is a free thread when the server calls back to itself to perform the test.

Brian Nesbitt

unread,
Sep 6, 2011, 9:02:57 AM9/6/11
to play-fr...@googlegroups.com
No one using JWebUnit?

dao

unread,
Oct 9, 2011, 7:36:53 PM10/9/11
to play-fr...@googlegroups.com
just to write it down, I had to put the jwebunit jars in my lib dir. Not straightforward as I had to remove the jar logback-classic-0.9.29 for the distrib as some classes are already present in the playframework

dao

unread,
Oct 9, 2011, 8:02:41 PM10/9/11
to play-fr...@googlegroups.com
Here is my first JWebUnit integration. I do this:

   

@Test
public void test1() {
beginAt("login");
setTextField("username", "myLogin");
setTextField("password", "myPassword");
clickButton("signin");
clickLink("supportedLayouts");
clickLink("newUplinkRequest");
String uplinkName = "my test uplink request";
setTextField("coolName", uplinkName);
clickLink("updateDescription");
clickLink("supportedLayouts");
assertTextPresent(uplinkName);
assertEquals(1,UplinkRequest.count());
UplinkRequest dbUplink = ((UplinkRequest)UplinkRequest.findAll().get(0)).refresh();
assertEquals(uplinkName, dbUplink.coolName);
 }


The last assert equals fails. I look in my base, it is OK (assert should not fail). Is it due to the %test.play.pool=2 ?
I use H2 mem database.  

Brian Nesbitt

unread,
Oct 10, 2011, 12:19:52 AM10/10/11
to play-fr...@googlegroups.com
There were a couple apache commons to be left out as well if I remember right.

Brian Nesbitt

unread,
Oct 10, 2011, 12:34:28 AM10/10/11
to play-fr...@googlegroups.com
From that test alone not sure I can see anything wrong? You are only fetching 1, do you have an @before that clears the database?
Maybe there is a bug in the save.
No max length on cool name?
Does it hang? Is the call to save the cool name an Ajax call ?

dao

unread,
Oct 10, 2011, 4:08:12 AM10/10/11
to play-fr...@googlegroups.com
you're right, ajax call. Is it possible to wait ajax ended?


--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To view this discussion on the web visit https://groups.google.com/d/msg/play-framework/-/3ImRbHH1oMYJ.
To post to this group, send email to play-fr...@googlegroups.com.
To unsubscribe from this group, send email to play-framewor...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/play-framework?hl=en.




--
Dao Hodac

Brian Nesbitt

unread,
Oct 10, 2011, 5:35:11 PM10/10/11
to play-fr...@googlegroups.com
Ya not that I can see.... that is probably the problem.  I just tried a simple ajax call test situation and had to put a Thread.sleep() to wait for the return.

      IElement e = wt.getElementById("ajaxret");
      assertEquals("fill me", e.getTextContent());

      wt.clickLink("trigger");                                 // has a jquery "click()" event to trigger an ajax event populate $("#ajaxret")
      play.Logger.info(e.getTextContent());            // still old value "fill me"
      Thread.sleep(2000);                                    // wait for some time
      play.Logger.info(e.getTextContent());            //now has new value
      assertEquals("successthis is from Application.ajax", e.getTextContent());   //passes


Brian Nesbitt

unread,
Oct 10, 2011, 5:35:42 PM10/10/11
to play-fr...@googlegroups.com
Ok... will update ... playing with  com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController

Brian Nesbitt

unread,
Oct 10, 2011, 6:00:28 PM10/10/11
to play-fr...@googlegroups.com
So this worked for me....   makes ajax calls synchronous...

Just after your call to beginAt() put this code:

if (wt.getTestingEngine() instanceof HtmlUnitTestingEngineImpl)
{
   ((HtmlUnitTestingEngineImpl) wt.getTestingEngine()).getWebClient().setAjaxController(new NicelyResynchronizingAjaxController());
}

This needs to go after beginAt() as the instance of the HtmlUnit WebClient is not created until beginAt() is called.

Also note, in my sample code above the "wt" variable is an instance of WebTester... it looks like you did the "import static" way so you can just remove wt and statically call getTestingEngine() in both places.



dao

unread,
Oct 10, 2011, 8:17:12 PM10/10/11
to play-fr...@googlegroups.com
excellent, it works perfeclty. thank's. I have tons of code lines to write now



--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To view this discussion on the web visit https://groups.google.com/d/msg/play-framework/-/PtpsUsn4aJ4J.

To post to this group, send email to play-fr...@googlegroups.com.
To unsubscribe from this group, send email to play-framewor...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/play-framework?hl=en.



--
Dao Hodac

Dominik Dorn

unread,
Oct 13, 2011, 8:31:01 AM10/13/11
to play-fr...@googlegroups.com
could anyone of you put a sample app with code at github?
or could summarize whats necessary to get this working seamlessly?

thanks,
dominik

--
Dominik Dorn
http://dominikdorn.com
http://twitter.com/domdorn

Skripten, Mitschriften, Lernunterlagen, etc. findest Du auf
http://www.studyguru.eu !

Brian Nesbitt

unread,
Oct 13, 2011, 12:03:00 PM10/13/11
to play-fr...@googlegroups.com
Yes I can do that later today.  Will re-post when its up.

Brian Nesbitt

unread,
Oct 16, 2011, 3:58:58 PM10/16/11
to play-fr...@googlegroups.com
I have (finally) posted a sample application with some explanation of the tests and how to make the ajax calls sync with JWebUnit.

Dominik Dorn

unread,
Oct 19, 2011, 2:24:40 PM10/19/11
to play-fr...@googlegroups.com
I'm just looking at your app.
I think you could do all the dependency stuff you put into your lib
folder with this single line
in conf/dependencies.yml

- net.sourceforge.jwebunit -> jwebunit-htmlunit-plugin 3.0

> --
> You received this message because you are subscribed to the Google Groups
> "play-framework" group.
> To view this discussion on the web visit

> https://groups.google.com/d/msg/play-framework/-/JF8SNDUHtQsJ.

Brian Nesbitt

unread,
Oct 19, 2011, 2:34:44 PM10/19/11
to play-fr...@googlegroups.com
I tried to use the dependencies file first awhile back but kept getting errors and/or not the right files downloaded.  Was just easier to get the files myself.
I am sure you are right though.

In "my" version I have the lib folder organized into folders: https://github.com/briannesbitt/nesbot.com/tree/master/lib
But this requires the patch I pushed to Play in the application.py file to create the classpath recursively.  It will be included in Play 1.2.4.

For simplicity sake, I left them all in the same folder so it would just work out of the box.

Dominik Dorn

unread,
Oct 19, 2011, 3:36:06 PM10/19/11
to play-fr...@googlegroups.com
yeah, I saw that in the commit log.

It works without a problem with the dependencies file here :)

> --
> You received this message because you are subscribed to the Google Groups
> "play-framework" group.
> To view this discussion on the web visit

> https://groups.google.com/d/msg/play-framework/-/iFSQzWkxA6sJ.

Dominik Dorn

unread,
Oct 20, 2011, 8:07:14 AM10/20/11
to play-fr...@googlegroups.com
I had to alter it to this, because logback was spamming me with messages:

- net.sourceforge.jwebunit -> jwebunit-htmlunit-plugin 3.0:
exclude:
- javax.servlet -> servlet-api
- ch.qos.logback -> *


How are you handling logins/sesssions? I've added thsoe methods in the
BaseFunctionalTest class:

protected Http.Cookie sessionToCookie(Scope.Session sessionStore){
StringBuilder session = new StringBuilder();
for (String key : sessionStore.all().keySet()) {
session.append("\u0000");
session.append(key);
session.append(":");
session.append(sessionStore.all().get(key));
session.append("\u0000");
}

String sessionData = null;
try {
sessionData = URLEncoder.encode(session.toString(), "utf-8");
String sign = Crypto.sign(sessionData, Play.secretKey.getBytes());

Http.Cookie cookie = new Http.Cookie();
cookie.name =
Play.configuration.getProperty("application.session.cookie",
"PLAY")+"_SESSION";
cookie.value = sign + "-" + sessionData;
cookie.domain = null;
cookie.path = "/";
cookie.maxAge = null;
cookie.secure = false;
cookie.httpOnly = false;

return cookie;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}

protected void setSession(Scope.Session session, String domain)
{
Http.Cookie cookie = sessionToCookie(session);
wt.getTestContext().addCookie(cookie.name, cookie.value,
Http.Request.current().host);
}


so now I can put stuff like this into my tests @Setup method:

Scope.Session session = new Scope.Session();
session.put("username", "dom...@host.com");
setSession(session);

wt.beginAt(getRouteAbsolute("sgcore.Account.index"));

I'm not quite sure if accessing the request in the helper method is
the right way.. is there always a request available in test-mode? or
is there another way to get to the current hostname?

Brian Nesbitt

unread,
Oct 20, 2011, 12:46:16 PM10/20/11
to play-fr...@googlegroups.com
I posted something like that awhile ago https://gist.github.com/1224015 for someone who wanted to test to ensure a session variable was being set.

But, I don't think you need to decode the PLAY_SESSION cookie actually for handling being logged in.
Just hit your login url with u/p in the @Before and ensure you send back the cookies for PLAY_SESSION, PLAY_FLASH, PLAY_ERRORS on your next request and you will be logged in.
You don't need to change them, just act as the user browsing would and send them back.

JWebUnit supplies functions to get/set the cookies.

And Yes, I think accessing Http.Request.current() isn't really correct - your going to be running in a different thread and those are stored in ThreadLocal vars (play.pool > 1).  You are running in the context of Play, but you aren't in the context of the "server" so to speak.  It would also be blank before the first request anyway.

If you still did need the host, you should know it since you had to set it when calling setBaseUrl().

SenthilKumar B

unread,
Jan 10, 2012, 11:23:04 PM1/10/12
to play-fr...@googlegroups.com
I have tried using the above jwebunit testcase. I got the following error.   Please help me in resolving this issue.
 
org.apache.http.conn.HttpHostConnectException: Connection to http://www.playframework.com refused
 at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:158)
 at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:149)
 at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:121)
 at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:573)
 at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:425)
 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:820)
 at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:776)
 at com.gargoylesoftware.htmlunit.HttpWebConnection.getResponse(HttpWebConnection.java:152)
 at com.gargoylesoftware.htmlunit.WebClient.loadWebResponseFromWebConnection(WebClient.java:1439)
 at com.gargoylesoftware.htmlunit.WebClient.loadWebResponse(WebClient.java:1358)
 at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:307)
 at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:373)
 at com.gargoylesoftware.htmlunit.WebClient.getPage(WebClient.java:358)
 at jweb.JWebClassExample2.testLogin(JWebClassExample2.java:27)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99)
 at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81)
 at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
 at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
 at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
 at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:71)
 at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
 at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
 at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
 at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.net.ConnectException: Connection refused: connect
 at java.net.DualStackPlainSocketImpl.connect0(Native Method)
 at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
 at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
 at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
 at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
 at java.net.PlainSocketImpl.connect(Unknown Source)
 at java.net.SocksSocketImpl.connect(Unknown Source)
 at java.net.Socket.connect(Unknown Source)
 at com.gargoylesoftware.htmlunit.SocksSocketFactory.connectSocket(SocksSocketFactory.java:89)
 at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:148)
 ... 33 more

Brian Nesbitt

unread,
Jan 12, 2012, 9:19:25 AM1/12/12
to play-fr...@googlegroups.com
Looks like you couldn't hit the play site.

Are you using this from a corporate environment?  My guess is you need to supply proxy information ?
Reply all
Reply to author
Forward
0 new messages