I will not develop my planned XPages Testing Framework further because for me it has no business case anymore. While there never was enough time to make it to a „real project“ for the daily development (full JUnit integration, deployed as an OSGi plugin etc) and this code is only an example, I used this technique with a similar code base in some projects. It was a big help during development, because it allows to „look inside“ the XPages engine while testing.
How does it work?
In a JUnit test, a local proxy is started which fetches all request / responses between the Selenium WebDriver and the Domino server. Only the first request is relevant, the other requests are the resources like Javascript files, Images and CSS.
During the test, a special HTTP Header is added to the request (X-XPAGES-TEST). It contains EL code and the JSF phase when to execute it. A phase listener (in the application, better in an OSGi plugin) intercepts the HTTP header and executes the code. The result it posted back to the client (encapsulated in another HTTP header). It can then be tested with an assert.
Because of the use of special headers instead of injecting the results into the HTML markup, this can be used with Diffy or similar products to verify the application while refactoring the code base.
Here is the JUnit Test application:
package ch.hasselba.xpages.test.XPagesTester;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import net.lightbody.bmp.core.har.Har;
import net.lightbody.bmp.proxy.ProxyServer;
import net.lightbody.bmp.proxy.http.BrowserMobHttpRequest;
import net.lightbody.bmp.proxy.http.BrowserMobHttpResponse;
import net.lightbody.bmp.proxy.http.RequestInterceptor;
import net.lightbody.bmp.proxy.http.ResponseInterceptor;
import org.apache.http.HttpResponse;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriver.Options;
import org.openqa.selenium.WebDriver.Timeouts;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
public class App
{
private ProxyServer server;
private LinkedList httpResponses;
private LinkedList httpRequests;
private WebDriver driver;
private String baseUrl = "http://localhost/Demo.nsf/Test.xsp";
private StringBuffer verificationErrors = new StringBuffer();
private final String HTTP_HEADER_NAME = "X-XPAGES-TESTS";
@Before
public void setUp() throws Exception {
this.server = new ProxyServer(4444);
this.server.start();
Proxy proxy = this.server.seleniumProxy();
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("proxy", proxy);
ResponseInterceptor interceptor = new ResponseInterceptor() {
public void process(BrowserMobHttpResponse response, Har har) {
httpResponses.add( response );
}
};
RequestInterceptor requestInterceptor = new RequestInterceptor() {
public void process(BrowserMobHttpRequest request, Har har) {
httpRequests.add( request );
}
};
this.server.addRequestInterceptor(requestInterceptor);
this.server.addResponseInterceptor(interceptor);
this.driver = new FirefoxDriver(capabilities);
this.driver.manage().timeouts().implicitlyWait(10L, TimeUnit.SECONDS);
}
@After
public void tearDown() throws Exception {
this.driver.quit();
String verificationErrorString = this.verificationErrors.toString();
if (!"".equals(verificationErrorString))
Assert.fail(verificationErrorString);
}
@Test
public void testDemo() throws Exception
{
StringBuffer strBuffer = new StringBuffer();
strBuffer.append("[");
strBuffer.append("{");
strBuffer.append("\"code\": \"#{javascript:java.lang.System.currentTimeMillis()}\"}]");
this.server.addHeader(HTTP_HEADER_NAME, strBuffer.toString() );
reloadPage();
System.out.println( "X-PAGES-TESTS: " + getXPageResponse().getHeader(HTTP_HEADER_NAME) );
HttpResponse httpRawResponse = getXPageResponse().getRawResponse();
Assert.assertTrue("HTTP/1.1 200 OK".equals(httpRawResponse.getStatusLine()
.toString()));
Assert.assertTrue("Lotus-Domino".equals(getXPageResponse().getHeader("Server")));
}
public void reloadPage()
{
this.httpRequests = new LinkedList();
this.httpResponses = new LinkedList();
this.driver.get(this.baseUrl);
}
public BrowserMobHttpResponse getXPageResponse(){
return this.httpResponses.get(0);
}
}
And here comes the phase listener:
package ch.hasselba;
import java.util.HashSet;
import java.util.Iterator;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.servlet.http.HttpServletRequest;
import com.ibm.commons.util.io.json.JsonEmptyFactory;
import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.commons.util.io.json.JsonJavaObject;
import com.ibm.commons.util.io.json.JsonParser;
import com.ibm.xsp.application.ApplicationEx;
import com.ibm.xsp.context.ExternalContextEx;
import com.ibm.xsp.context.FacesContextExImpl;
@SuppressWarnings("serial")
public class TestPhaseListener implements PhaseListener {
private final String HTTP_HEADER_NAME = "X-XPAGES-TESTS";
public void afterPhase(PhaseEvent event) {}
public void beforePhase(PhaseEvent event) {
FacesContextExImpl fc = (FacesContextExImpl) event.getFacesContext();
ExternalContextEx ec = (ExternalContextEx) fc.getExternalContext();
HttpServletRequest request = (HttpServletRequest) ec.getRequest();
ApplicationEx app = fc.getApplicationEx();
HashSet results = new HashSet();
try {
String jsonData = request.getHeader(HTTP_HEADER_NAME);
// Create the JAVA json factory
JsonJavaFactory factory = JsonJavaFactory.instanceEx;
// Turn the sort parameter string into an array of JSON objects
Object jsonTests;
jsonTests = JsonParser.fromJson(factory, jsonData);
// Loop through the JSON objects and do something
for (Iterator<Object> oSort = factory.iterateArrayValues(jsonTests); oSort
.hasNext();) {
JsonJavaObject o = (JsonJavaObject) oSort.next();
//String phaseId o.getJsonProperty("phaseId") );
String code = (String) o.getJsonProperty("code");
try {
Object result = app.createMethodBinding(code, null).invoke(fc,
null);
results.add( result.toString() );
}catch( Exception e) {
results.add( e.getMessage() );
}
}
StringBuffer buffer = new StringBuffer();
buffer.append( "[" );
int pos = 0;
for( String res : results ) {
buffer.append( "{ \"result\" : \"" + res + "\"}");
pos++;
if( pos < results.size() )
buffer.append( "," );
}
buffer.append( "]");
fc.getXspResponse().addHeader(HTTP_HEADER_NAME, buffer.toString() );
} catch (JsonException e) {
e.printStackTrace();
fc.getXspResponse().addHeader(HTTP_HEADER_NAME,
e.getLocalizedMessage());
} catch (Exception e) {
e.printStackTrace();
fc.getXspResponse().addHeader(HTTP_HEADER_NAME,
e.getLocalizedMessage());
}
}
public PhaseId getPhaseId() {
return PhaseId.ANY_PHASE;
}
}
Hope someone finds it usefull.