EntwicklerCamp 2016: XPages erweitern und ausbauen

Die PDF Version meiner Entwickler Camp 2016 Session „XPages erweitern und ausbauen“ gibt es hier: ec2016-Votrag-XPagesErweiternUndAusbauen.pdf

Die verwendeten Sources finden sich in diesem Repository auf GitHub: ec2016-xpages-erweitern

Bei Fragen/Problemen einfach eine kurze Info an mich.

Veröffentlicht unter EC 2016, XPages | Verschlagwortet mit , | Hinterlasse einen Kommentar

EntwicklerCamp 2016: Ein Leben ohne Notes Client

Hier gibt es die PowerPoint Präsentation von meiner Entwickler Camp 2016 Session „Ein Leben ohne Notes Client“: ec2016-Votrag-LebenOhneNotesClient.pptx

Die kompletten Sources finden sich in diesem Repository auf GitHub: ec2016-ein-leben-ohne-notesclient

Bei Fragen/Problemen einfach eine kurze Info an mich.

UPDATE: Hier die PDF Version.

Veröffentlicht unter EC 2016, Java, JavaFX | Verschlagwortet mit , , | Hinterlasse einen Kommentar

Kurze Werbeunterbrechung

Probleme bei der XPages-Entwicklung? Schulung gefällig?
Fehlt die eine oder andere Komponente? Oder wird ein neuer Renderer benötigt, der zur Abwechslung mal den HTML Code generiert, den man gerne hätte?

Ich biete gerne meine Unterstützung an.
Einfach mal Kontakt aufnehmen. Gern auch per Mail: contact <at> hasselba.ch

Veröffentlicht unter Allgemein | Hinterlasse einen Kommentar

How To Crash a Domino Server in 500ms

How To Crash a Domino Server in 500ms

1. Create a Java agent and do something in your finally block (or in a ThreadDeath exception handling) which runs longer than 500ms

import lotus.domino.AgentBase;

public class JavaAgent extends AgentBase {

    public void NotesMain() {
        try {
            int i = 0;
            while (i < 1000) {
                i++;
                System.out.println("Round " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {}
            }
        } finally {
            try {
                Thread.sleep(1000);
            } catch (Exception e) {}
        }
    }
}

2. Run the agent on the server

2016-02-20 11_31_17-SH Domain - Dev01_Hasselba_CH - IBM Domino Administrator1

3. Quit the Agent Manager

2016-02-20 11_31_17-SH Domain - Dev01_Hasselba_CH - IBM Domino Administrator2

4. And then restart it

2016-02-20 11_31_17-SH Domain - Dev01_Hasselba_CH - IBM Domino Administrator3

5. Enjoy the silence

2016-02-20 11_31_17-SH Domain - Dev01_Hasselba_CH - IBM Domino Administrator4

Fun fact: This works well in a IBM Notes Client too! Just start the agent and cancel it a few times (<CTRL> + <BREAK>).

Veröffentlicht unter Agenten, Bug, Java, Server | Verschlagwortet mit , , , | Hinterlasse einen Kommentar

Vaadin IBM Challenge

Since two weeks the Vaadin IBM challenge is over. While I was only able to do the bare minimum, I really enjoyed the different tasks (see here for list of steps).

Congrats to the winners! And thanks to the Vaading Team & IBM for this exciting experience.

Veröffentlicht unter Vaadin | 4 Kommentare

Things I never blogged about: XPages & Google’s EventBus

This is another topic I wanted to blog about for a long time: The use of Google’s EventBus in XPages applications. EventBus is a replacement for the Java in-process event distribution. It makes life a lot easier.

My first plan was to add the guava.jar into an OSGi plugin because of security reasons (otherwise you have to use the „grant{ permission java.security.AllPermission;}„) but I never had the time. That’s why I created a simple example which uses the jar in the lib/ext folder instead.

To use the EventBus in an application, you first have to define an event. This simple example has a message only, but you can add any properties you want.

package ch.hasselba.xpages;

public class FrontendEvent {

    private final String message;

    public FrontendEvent(final String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

}

Then we need a subscriber. For this we have to add the annotation @Subscribe to a method of a managed bean which should handle an event. The class type of the parameter tells the EventBus what kind of event our method should handle.

This is a managed bean which stores the last message sent over the EventBus:

package ch.hasselba.xpages;

import com.google.common.eventbus.Subscribe;

public class EventSubscriber {

    private String message;

    @Subscribe
    public void handleEvent(FrontendEvent event) {
        this.message = event.getMessage();
    }

    public void getMessage(String msg) {
        this.message = msg;
    }

    public String getMessage() {
        return this.message;
    }
}

Then we have to create a managed bean which has an instance of EventBus, the FrontendEventBus. The bus has a managed property subscribers. As soon the bean is instantiated, the managed property is called and the list of objects is registered to the EventBus instance.

package ch.hasselba.xpages;

import java.io.Serializable;
import java.util.ArrayList;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionContext;
import com.google.common.eventbus.SubscriberExceptionHandler;

@SuppressWarnings("serial")
public class FrontendEventBus implements SubscriberExceptionHandler,
        Serializable {

    final EventBus eventBus = new EventBus();
    ArrayList<Object> subscribers;

    /**
     * registers all subscribing managed beans from faces-config.xml
     * 
     * @param subscribers
     *            ArrayList with references to managed beans
     */
    public void setSubscribers(ArrayList<Object> subscribers) {
        this.subscribers = subscribers;
        for (Object subscriber : subscribers) {
            register(subscriber);
        }
    }

    public void post(final Object event) {
        eventBus.post(event);
    }

    public void register(final Object object) {
        eventBus.register(object);
    }

    public void unregister(final Object object) {
        eventBus.unregister(object);
    }

    public final void handleException(Throwable exception,
            SubscriberExceptionContext context) {
        exception.printStackTrace();

    }

}

There is only one problem: If a bean is in a lower scope, the initialization will fail. In this case we have to do the opposite to attach a subscriber to the bus. Instead of telling the bus which subscriber we want to register, we are telling the subscriber which bus to attach to.

package ch.hasselba.xpages;

public class EventSubscriberRequestScope extends EventSubscriber {

    /**
     * registers the current instance to the event bus
     * 
     * @param eventBus
     *            the frontend event bus
     */
    public void setEventBus(FrontendEventBus eventBus) {
        eventBus.register(this);
    }

}

As soon the bean is instantiated, the managed property will have an instance of our FrontendEventBus and the bean can register itself.

The faces-config.xml looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>

    <managed-bean>
        <managed-bean-name>frontendEventBus</managed-bean-name>
        <managed-bean-class>ch.hasselba.xpages.FrontendEventBus</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
        <managed-property>
            <property-name>subscribers</property-name>
            <list-entries>
                <value-class>java.lang.Object</value-class>
                <value>#{eventSubscriber}</value>
            </list-entries>
        </managed-property>
    </managed-bean>
    
    
    <managed-bean>
        <managed-bean-name>eventSubscriber</managed-bean-name>
        <managed-bean-class>ch.hasselba.xpages.EventSubscriber</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
    </managed-bean>
    
    
    <managed-bean>
        <managed-bean-name>eventSubscriberRequestScope</managed-bean-name>
        <managed-bean-class>ch.hasselba.xpages.EventSubscriberRequestScope</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>eventBus</property-name>
            <value>#{frontendEventBus}</value>
        </managed-property>
    </managed-bean>
    
</faces-config>

The first subscribing bean is in the same scope as the FrontendEventBus, but the second one is a request scoped bean.

Now it’s time to send an event. This can be realized by creating a new instance of our FrontendEvent we have created earlier and posting it to the bus.

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:button
        id="button1" value="Click me">
        
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:importPackage( ch.hasselba.xpages );
                    var msg = "Timestamp: " + java.lang.System.currentTimeMillis();
                    var se = new ch.hasselba.xpages.FrontendEvent( msg );
                    frontendEventBus.post( se );
                }]]>
            </xp:this.action>
        </xp:eventHandler>
    
    </xp:button>
    
    <br />
    <br />
    
    <xp:label
        value="#{eventSubscriber.message}"
        id="labelSubscriberMessage">
    </xp:label>
    
    <br />
    
    <xp:label
        value="#{eventSubscriberRequestScope.message}"
        id="labelSubscriberRequestScope">
    </xp:label>
    
</xp:view>

As soon the button is clicked, both beans are receiving the event and updating their message.

2015-12-01 14_04_04-Mozilla Firefox

Hope someone finds it usefull.

Veröffentlicht unter Java, JSF, XPages | Hinterlasse einen Kommentar

Things I never blogged about: The XPagesExecutor Service

The XPages engine has its own executor service to run jobs concurrently in another thread: the XPagesExecutor service. Under the hood the service uses a ThreadPoolExecutor for executing tasks, so it allows to use Runnables or Callables/Futures for asynchronous computation. For fun, you can even run a server instance, the started threads can run as long as you want (e.g. they are not bound to the XPages lifecycle).

Here is a small XPage with a button to start a Runnable via the Executor:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    
    <xp:button
        value="run the job"
        id="buttonDoIt">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete">
            <xp:this.action>
                <![CDATA[#{javascript:
                importPackage(ch.hasselba.xpages);
                new ch.hasselba.xpages.DemoExecutor().doIt();}]]>
            </xp:this.action>
        </xp:eventHandler>
    </xp:button>
    
</xp:view>

To use the Service, you have to aquire it first, then you can execute a Runnable like in this code snippet:

package ch.hasselba.xpages;

import java.util.concurrent.ExecutorService;

import lotus.domino.NotesException;
import lotus.domino.Session;

import com.ibm.domino.xsp.module.nsf.NSFComponentModule;
import com.ibm.domino.xsp.module.nsf.NotesContext;
import com.ibm.domino.xsp.module.nsf.SessionCloner;
import com.ibm.xsp.application.XPagesExecutor;

public class DemoExecutor {
    private final NSFComponentModule module;
    private final SessionCloner sessionCloner = SessionCloner
            .getSessionCloner();

    public DemoExecutor() {
        // get the current NSFComponentModule
        this.module = NotesContext.getCurrent().getModule();
    }

    public void doIt() {
        // aquire the Executor
        ExecutorService exService = XPagesExecutor.acquire();
        
        // run the Runnable
        exService.execute(new DemoRunnable(this.module));
    }

    class DemoRunnable implements Runnable {
        private final NSFComponentModule module;

        public DemoRunnable(NSFComponentModule module) {
            this.module = module;
        }

        public void run() {
            try {

                // init the NotesThread
                NotesContext context = new NotesContext(this.module);
                NotesContext.initThread(context);

                // now do the job...
                System.out.println("Starting the job...");
                Session session = null;
                try {
                    // grab the session
                    session = sessionCloner.getSession();
                    System.out.println("Running as "
                            + session.getEffectiveUserName());
                    Thread.sleep(10000);
                } catch (NotesException e) {
                    e.printStackTrace();
                } finally {
                    DominoUtils.recycle( session );
                }
                System.out.println("Done!");

            } catch (InterruptedException ie) {
                // handle interruption here
            } finally {
                // kill the Notes thread
                NotesContext.termThread();
            }
        }

    }

}

2015-11-29 08_54_13-SH Domain - Dev01_Hasselba_CH - IBM Domino Administrator

Callables must be submitted instead:

   public long doItWithCallable() {

        ExecutorService exService = XPagesExecutor.acquire();
        Future<Long> result = exService.submit(new MyCallable(this.module));
        long returnValue = (-1);

        try {
            returnValue = result.get();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        } catch (ExecutionException ee) {
            ee.printStackTrace();
        }

        return returnValue;
    }

    class DemoCallable implements Callable {
        private final NSFComponentModule module;

        public DemoCallable(NSFComponentModule module) {
            this.module = module;
        }

        public Long call() throws Exception {
            try {

                // init the NotesThread
                NotesContext context = new NotesContext(this.module);
                NotesContext.initThread(context);

                // now do the job...
                System.out.println("Starting the job...");
                Session session = null;
                try {
                    // grab the session
                    session = sessionCloner.getSession();
                    System.out.println("Running as "
                            + session.getEffectiveUserName());
                    Thread.sleep(10000);
                } catch (NotesException e) {
                    e.printStackTrace();
                } finally {
                    DominoUtils.recycle( session );
                }
                System.out.println("Done!");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // kill the Notes thread
                NotesContext.termThread();
            }

            return System.currentTimeMillis();
        }

    }

Hope someone finds it usefull.

Veröffentlicht unter Java, XPages | 1 Kommentar

[Discontinued] Testing XPages(3): Testing the Business Logic

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.

Veröffentlicht unter Java, XPages | Hinterlasse einen Kommentar

Debug Retrofit REST applications

Today I had some problems with designing a Retrofit REST application, so I needed a way to debug the request and the response from the server. After poking around, I found the HttpLoggingInterceptor from OkHttp.  It provides all the functionality I need and is really easy to implement.

First you have create a OkHttpClient instance, add the interceptor and set the debug level to HttpLoggingInterceptor.Level.BODY:

OkHttpClient client = new OkHttpClient();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
client.interceptors().add(interceptor);    

Next step is to add the client during the building of the Retrofit instance:

Retrofit retrofit = new Retrofit.Builder().client(client).build();

Now you can see the HTTP data sent over the wire:

2015-11-27 21_11_08

Don’t forget to add the two required dependencies to your pom.xml:

<dependency>
   <groupId>com.squareup.okhttp</groupId>
   <artifactId>okhttp</artifactId>
   <version>2.6.0</version>
</dependency>
<dependency>
   <groupId>com.squareup.okhttp</groupId>
   <artifactId>logging-interceptor</artifactId>
   <version>2.6.0</version>
</dependency>
Veröffentlicht unter Java, REST, Web | Hinterlasse einen Kommentar

Testing XPages (2): BrowserMob Proxy

When testing XPages or other web applications, you may want to have more control about the requests and responses during the JUnit testing. For example, if you want to test if a specific HTTP header exists in the response, or if it is required to add some HTTP headers to the request. But you cannot doe this out of the box with Selenium. Instead, you have to add a proxy to the Firefox controller, which then gives you a handle to the HTTP data and allows you to control the flow.

An good solution for this is BrowserMob Proxy, which can be used by adding the required dependency to your Maven pom.xml:

<dependency>
    <groupId>net.lightbody.bmp</groupId>
    <artifactId>browsermob-proxy</artifactId>
    <version>2.0.0</version>
</dependency>

[This is version 2.0.0, the latest stable version]

The proxy runs locally on the specified port as soon the JUnit test starts and ends automatically after finishing the tests.  In order to accomplish this, the setUp method has to be modified:

// start the proxy (listens on port 4444)
server = new ProxyServer(4444);
server.start();

// get the Selenium proxy object
Proxy proxy = server.seleniumProxy();

// configure it as a desired capability
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability(CapabilityType.PROXY, proxy);

Now, the proxy setting must be added to the Firefox driver:

driver = new FirefoxDriver(capabilities);

In the test class, three global variables must be defined; these are giving you access to the proxy server and latest the request and response during testing:

private ProxyServer server;
private BrowserMobHttpResponse httpResponse;
private BrowserMobHttpRequest httpRequest;

With the help of a ResponseInterceptor, the httpResponse property is always filled with the latest response. To initialize it, an anonymous class in the setUp method has to be created:

// add a response interceptor
ResponseInterceptor interceptor = new ResponseInterceptor() {
    public void process(BrowserMobHttpResponse response, Har har) {
          httpResponse = response;
    }
};

// add the interceptor to the server
server.addResponseInterceptor(interceptor);

For the RequestInterceptor it is the same procedure:

// add a request interceptor
RequestInterceptor requestInterceptor = new RequestInterceptor() {
    public void process(BrowserMobHttpRequest request, Har har) {
       httpRequest = request;
    }
};

server.addRequestInterceptor(requestInterceptor);

Now, it is possible to use it in a JUnit test:

@Test
public void testDemo() throws Exception {
 
    // add a request header
    server.addHeader("X-FOO", "BAR");
 
    // load the page
    reloadPage();

    // TEST RESPONSE STATUS
    HttpResponse httpRawResponse = httpResponse.getRawResponse();
    assertTrue("HTTP/1.1 200 OK".equals(httpRawResponse.getStatusLine()
       .toString()));
 
    // TEST SERVER HEADER
    assertTrue( "Lotus-Domino".equals( httpResponse.getHeader("Server")) );
 
}

The Browsermob-proxy has a lot of additional features: You can modify the allowed speed for up- and downstreams, use basic authentication, upload files, etc.

Veröffentlicht unter Java, Web, XPages | Verschlagwortet mit , , , , , , , | Hinterlasse einen Kommentar