diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractGWTTestCase.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractGWTTestCase.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f219f072dd0d3c43358a93827fa2a0c4fee7323
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/AbstractGWTTestCase.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Abstract super class of all GWT System Tests.
+ *
+ * @author Franz-Josef Elmer
+ */
+public abstract class AbstractGWTTestCase extends GWTTestCase
+{
+    
+    @Override
+    public String getModuleName()
+    {
+        return "ch.systemsx.cisd.openbis.OpenBIS";
+    }
+    
+    /**
+     * Terminates test. Wrapper of {@link #finishTest()}. Will be used in {@link RemoteConsole}.
+     */
+    void terminateTest()
+    {
+        finishTest();
+    }
+    
+    /**
+     * Delays test termination until the specified timeout (in milliseconds). Wrapper of
+     * {@link #delayTestFinish(int)}. Will be used in {@link RemoteConsole}.
+     */
+    void delayTestTermination(int timeoutMillis)
+    {
+        delayTestFinish(timeoutMillis);
+    }
+    
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/CallbackClassCondition.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/CallbackClassCondition.java
new file mode 100644
index 0000000000000000000000000000000000000000..c097ee417321866b6ceecfb7444f0347dba3ca01
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/CallbackClassCondition.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * Contition which checks whether the class of the callback object is as specified.
+ *
+ * @author Franz-Josef Elmer
+ */
+public class CallbackClassCondition implements ICallbackCondition<Object>
+{
+    private final Class<?> callbackClass;
+
+    /**
+     * Creates an instance for the specified callback class.
+     */
+    public CallbackClassCondition(Class<? extends AsyncCallback<?>> callbackClass)
+    {
+        this.callbackClass = callbackClass;
+    }
+    
+    public boolean valid(AsyncCallback<Object> callback, Object result)
+    {
+        return callbackClass.equals(callback.getClass());
+    }
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ClientTest.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ClientTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..01dac981b8174050b07136a494b3c966688cf827
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ClientTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.LoginCommand;
+
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class ClientTest extends AbstractGWTTestCase
+{
+
+    private RemoteConsole remoteConsole;
+    
+    
+    @Override
+    protected void gwtSetUp() throws Exception
+    {
+        remoteConsole = new RemoteConsole(this);
+    }
+
+    public void testLogin() throws Exception
+    {
+        final Client client = new Client();
+        client.onModuleLoad();
+        remoteConsole.prepare(new LoginCommand("test", "blabla")).finish(10000);
+    }
+    
+    public void testFailedLogin() throws Exception
+    {
+        final Client client = new Client();
+        client.onModuleLoad();
+        remoteConsole.prepare(new LoginCommand("", "")).finish(10000);
+    }
+    
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GWTTestUtil.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GWTTestUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..76f84468496bb64d6baba414d577a73c3aa8eb7f
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/GWTTestUtil.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import java.util.List;
+
+import junit.framework.Assert;
+
+import com.extjs.gxt.ui.client.widget.Component;
+import com.extjs.gxt.ui.client.widget.Container;
+import com.extjs.gxt.ui.client.widget.ContentPanel;
+import com.extjs.gxt.ui.client.widget.button.Button;
+import com.extjs.gxt.ui.client.widget.form.TextField;
+import com.google.gwt.user.client.ui.ComplexPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Useful static methods for testing.
+ *
+ * @author Franz-Josef Elmer
+ */
+public class GWTTestUtil
+{
+    private GWTTestUtil()
+    {
+    }
+    
+    /**
+     * Gets the {@link Button} with specified id.
+     * 
+     * @throws AssertionError if not found or isn't a text field.
+     */
+    public static Button getButtonWithID(String id)
+    {
+        Widget widget = tryToFindByID(id);
+        Assert.assertNotNull("Button '" + id + "' not found.", widget);
+        Assert.assertTrue("Widget '" + id + "' isn't a Button: " + widget.getClass(),
+                widget instanceof Button);
+        return (Button) widget;
+    }
+    
+    /**
+     * Gets the {@link TextField} with specified id.
+     * 
+     * @throws AssertionError if not found or isn't a text field.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> TextField<T> getTextFieldWithID(String id)
+    {
+        Widget widget = tryToFindByID(id);
+        Assert.assertNotNull("Text field '" + id + "' not found.", widget);
+        Assert.assertTrue("Widget '" + id + "' isn't a TextField: " + widget.getClass(),
+                widget instanceof TextField);
+        return (TextField<T>) widget;
+    }
+    
+    /**
+     * Tries to find the Widget of specified type with specified id.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends Widget> T tryToFindByID(Class<T> widgetClass, String id)
+    {
+        return (T) tryToFindByID(id);
+    }
+    
+    /**
+     * Tries to find the widget with specified id.
+     * 
+     * @return <code>null</code> if not found.
+     */
+    public static Widget tryToFindByID(String id)
+    {
+        return tryToFindByID(RootPanel.get(), id);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Widget tryToFindByID(Widget widget, String id)
+    {
+        Widget result = null;
+        if (id.equals(widget.getElement().getId()))
+        {
+            result = widget;
+        } else if ((widget instanceof Component) && id.equals(((Component) widget).getId()))
+        {
+            result = widget;
+        } else if (widget instanceof ComplexPanel)
+        {
+            ComplexPanel panel = (ComplexPanel) widget;
+            for (int i = 0, n = panel.getWidgetCount(); i < n && result == null; i++)
+            {
+                result = tryToFindByID(panel.getWidget(i), id);
+            }
+        } else if (widget instanceof Container)
+        {
+            Container<Component> container = (Container<Component>) widget;
+            List<Component> items = container.getItems();
+            for (int i = 0, n = items.size(); i < n && result == null; i++)
+            {
+                result = tryToFindByID(items.get(i), id);
+            }
+            if (result == null && widget instanceof ContentPanel)
+            {
+                ContentPanel contentPanel = (ContentPanel) widget;
+                List<Button> buttons = contentPanel.getButtonBar().getItems();
+                for (int i = 0, n = buttons.size(); i < n && result == null; i++)
+                {
+                    result = tryToFindByID(buttons.get(i), id);
+                }
+            }
+        }
+        return result;
+    }
+
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ICallbackCondition.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ICallbackCondition.java
new file mode 100644
index 0000000000000000000000000000000000000000..83f05b50bab0d3f2a033e8aec85916d93ea316cd
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ICallbackCondition.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * Condition a callback object and/or the result delivered by the callback object have
+ * to fulfilled in order to execute a {@link ITestCommand} in {@link RemoteConsole}.
+ *
+ * @author Franz-Josef Elmer
+ */
+public interface ICallbackCondition<T>
+{
+    /**
+     * Returns <code>true</code> if the condition is fulfilled.
+     */
+    public boolean valid(AsyncCallback<T> callback, T result);
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ITestCommand.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ITestCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..d72a356f9f1737f73b00b2147d58808a26b43277
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ITestCommand.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import org.aopalliance.intercept.Invocation;
+
+/**
+ * A commad which will be executed after a successful {@link Invocation} of
+ * {@link AbstractAsyncCallback#process(Object)}.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public interface ITestCommand
+{
+    /**
+     * Executes this command.
+     */
+    public void execute();
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ITestCommandWithCondition.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ITestCommandWithCondition.java
new file mode 100644
index 0000000000000000000000000000000000000000..96ec8b560d3107989da2859019236782a4d5a588
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ITestCommandWithCondition.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+/**
+ * Interface of all test commands which knows also their condition for execution.
+ *
+ * @author Franz-Josef Elmer
+ */
+public interface ITestCommandWithCondition<T> extends ITestCommand, ICallbackCondition<T>
+{
+
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/RemoteConsole.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/RemoteConsole.java
new file mode 100644
index 0000000000000000000000000000000000000000..16e4f10770e684ca798d7c351a42b9d005647893
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/RemoteConsole.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.Assert;
+
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * A class which allows to execute a sequence of {@link ITestCommand} instances. The commands are
+ * executed in the order they have been added by the various prepare methods. A
+ * {@link ICallbackCondition} has to be fulfilled before the next command is executed. This
+ * condition is checked after each successful invocation of
+ * {@link AbstractAsyncCallback#process(Object)}. With the method {@link #finish(int)} a timeout
+ * will be specified after which the test will be terminated independent whether all commands have
+ * been executed or not. In the later case the test fails.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public class RemoteConsole
+{
+    private static final class CommandEntry 
+    {
+        private final ICallbackCondition<Object> condition;
+        private final ITestCommand command;
+        CommandEntry(ICallbackCondition<Object> condition, ITestCommand command)
+        {
+            this.condition = condition;
+            this.command = command;
+        }
+    }
+    
+    private class EqualsCallback implements ICallbackCondition<Object>
+    {
+        private final AsyncCallback<?> callbackToCheck;
+        
+        public EqualsCallback(AsyncCallback<?> callback)
+        {
+            this.callbackToCheck = callback;
+        }
+        
+        public boolean valid(AsyncCallback<Object> callback, Object result)
+        {
+            return callbackToCheck.equals(callback);
+        }
+    }
+    
+    private final AbstractGWTTestCase testCase;
+    private final List<CommandEntry> entries;
+    
+    private int entryIndex;
+    
+    /**
+     * Creates an instance for the specified test.
+     */
+    public RemoteConsole(final AbstractGWTTestCase testCase)
+    {
+        this.testCase = testCase;
+        entries = new ArrayList<CommandEntry>();
+        AbstractAsyncCallback.setCallbackListener(new ICallbackListener()
+            {
+                public void onFailureOf(AsyncCallback<Object> callback, Throwable throwable)
+                {
+                    Assert.fail("Failed condition " + callback + ": " + throwable);
+                }
+
+                public void startOnSuccessOf(AsyncCallback<Object> callback, Object result)
+                {
+                }
+
+                public void finishOnSuccessOf(AsyncCallback<Object> callback, Object result)
+                {
+                    System.out.println(entryIndex + "/" + entries.size() + ": " + callback);
+                    if (entryIndex < entries.size()
+                            && entries.get(entryIndex).condition.valid(callback, result))
+                    {
+                        CommandEntry commandEntry = entries.get(entryIndex++);
+                        commandEntry.command.execute();
+                        if (entryIndex == entries.size())
+                        {
+                            testCase.terminateTest();
+                        }
+                    }
+                }
+
+            });
+    }
+
+    /**
+     * Prepares the console with the specified command.
+     */
+    public RemoteConsole prepare(ITestCommandWithCondition<Object> command)
+    {
+        return prepare(command, command);
+    }
+    
+    /**
+     * Prepares the console with the specified command which will be executed if the
+     * class of the callback object is as specified. 
+     */
+    public RemoteConsole prepare(Class<? extends AsyncCallback<?>> clazz, ITestCommand command)
+    {
+        return prepare(new CallbackClassCondition(clazz), command);
+    }
+    
+    /**
+     * Prepares the console with the specified command which will be executed if the
+     * callback object equals the specified one. 
+     */
+    public RemoteConsole prepare(AsyncCallback<?> callback, ITestCommand command)
+    {
+        return prepare(new EqualsCallback(callback), command);
+    }
+
+    /**
+     * Prepares the console with the specified command which will be executed if the
+     * specified condition is fulfilled. 
+     */
+    public RemoteConsole prepare(ICallbackCondition<Object> condition, ITestCommand command)
+    {
+        entries.add(new CommandEntry(condition, command));
+        return this;
+    }
+    
+    /**
+     * Sets the timeout after which the test is terminated.
+     * 
+     * @throws AssertionError if not all commands have been executed.
+     */
+    public void finish(int delayInMilliseconds)
+    {
+        new Timer()
+            {
+                @Override
+                public void run()
+                {
+                    AbstractAsyncCallback.setCallbackListener(null);
+                    int numberOfUnexcutedCommands = entries.size() - entryIndex;
+                    if (numberOfUnexcutedCommands > 0)
+                    {
+                        Assert.fail("Console not finished. Last "
+                                + (numberOfUnexcutedCommands == 1 ? "command has"
+                                        : numberOfUnexcutedCommands + " commands have")
+                                + " not been executed.");
+                    }
+                }
+            }.schedule(delayInMilliseconds);
+       testCase.delayTestTermination(delayInMilliseconds + 1000);
+    }
+}
diff --git a/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginCommand.java b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..fad38b60aba0424c46c6b1c20d225e4c5a3cffba
--- /dev/null
+++ b/openbis/sourceTest/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/LoginCommand.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2008 ETH Zuerich, CISD
+ *
+ * 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 ch.systemsx.cisd.openbis.generic.client.web.client.application.ui;
+
+import com.extjs.gxt.ui.client.Events;
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.CallbackClassCondition;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.GWTTestUtil;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ITestCommandWithCondition;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.SessionContextCallback;
+
+/**
+ * Command for login after {@link SessionContextCallback} has finished.
+ *
+ * @author Franz-Josef Elmer
+ */
+public class LoginCommand extends CallbackClassCondition implements ITestCommandWithCondition<Object>
+{
+    private final String user;
+    private final String password;
+
+    public LoginCommand(String user, String password)
+    {
+        super(SessionContextCallback.class);
+        this.user = user;
+        this.password = password;
+    }
+
+    @SuppressWarnings("unchecked")
+    public void execute()
+    {
+        GWTTestUtil.<String>getTextFieldWithID(LoginWidget.USER_FIELD_ID).setValue(user);
+        GWTTestUtil.<String>getTextFieldWithID(LoginWidget.PASSWORD_FIELD_ID).setValue(password);
+        GWTTestUtil.getButtonWithID(LoginWidget.BUTTON_ID).fireEvent(Events.Select);
+    }
+
+}