UI Testing Talk at EclipseCon08

WindowTester allows you to easily create and run unit tests for every GUI they build. It can also be used to generate system level tests.

Moderators: gnebling, Eric Clayberg, Dan Rubel, keertip, Phil Quitslund

UI Testing Talk at EclipseCon08

Postby Phil Quitslund » Sun Feb 03, 2008 8:37 pm

If you're going to EclipseCon, check out our short talk (http://www.eclipsecon.org/2008/?page=sub/&id=238). Better yet, stop by our booth and introduce yourselves. It would be great to get a chance to connect in person.

Hope to see you there!

-phil
--
Phil Quitslund
Software Engineer
Google, Inc.
Phil Quitslund
Moderator
 
Posts: 491
Joined: Fri Apr 28, 2006 6:26 am

Re: UI Testing Talk at EclipseCon08

Postby mwa » Mon Jun 16, 2008 5:14 am

thank you for that interesting slides. It is a very good idea to create helpers for the typical gui tasks so the fixtures are deliverable. Im writing gui tests for an xml editor at the moment and like to ask if there are any additional helpers you can provide except those in EclipseTest.java. A helpers package would be great. Especially if someone is new to gui testing :oops:

Michael
mwa
 
Posts: 24
Joined: Mon Jun 16, 2008 4:38 am

Re: UI Testing Talk at EclipseCon08

Postby Phil Quitslund » Mon Jun 16, 2008 12:47 pm

Hi Michael,

We're working on parceling up some helpers for more popular consumption. We expect to make this available soon after our upcoming Ganymede release. If you have specific things you'd like to see, please send your requests to

wintest-support@instantiations.com

Many Thanks!

-phil
--
Phil Quitslund
Software Engineer
Google, Inc.
Phil Quitslund
Moderator
 
Posts: 491
Joined: Fri Apr 28, 2006 6:26 am

Re: UI Testing Talk at EclipseCon08

Postby mwa » Mon Jun 16, 2008 11:27 pm

Hi Phil,

that would be great. Thanks. I will mail any specific questions. First I have a more general Problem. In the slides a class WorkbenchHelper is mentioned. Containing the static method:
Code: Select all
public static void createProject(String projectName) {
click(menu("File/New/Other..."));
waitFor(shellShowing("New"));
click(tree("General/Project"));
click(button("Next"));
enter(projectName);
click(button("Finish"));
waitFor(shellDisposed("New Project"));
assertThat(projectExists(projectName));
}

I tried to realize that. My test class uses the WorkbenchHelper as a member. In the class WorkbenchHelber I have the following (because an ui object is needed):
Code: Select all
/**
* Contains some static helper methods.
*/
public final class WorkbenchHelper extends UITestCaseSWT {
   /**
    * Create a simple project with the given name.
    */
   public void createSimpleProject(String projectName) throws Exception {
      
      IUIContext ui = getUI();
      ui.click(new MenuItemLocator("&File/&New\tAlt+Shift+N/P&roject..."));
      ui.wait(new ShellShowingCondition("New Project"));
      ui.click(new TreeItemLocator("General/Project"));
      ui.click(new ButtonLocator("&Next >"));
      ui.enterText(projectName);
      ui.click(new ButtonLocator("&Finish"));
      ui.wait(new ShellDisposedCondition("project.creation.dialog"));
      //wait for the project to be created
      new ConditionHelper().waitForProject(projectName);
   }

The test class looks like this:
Code: Select all
public class MyTest extends UITestCaseSWT {

   private WorkbenchHelper workbenchHelper;

   /**
    * Set up is done before every test case.
    */
   @Override
   protected void setUp() throws Exception {
      this.workbenchHelper = new WorkbenchHelper();
      this.conditionHelper = new ConditionHelper();
      closeWelcomePageIfNecessary();
   }
   
   
   /**
    * A test that creates a new project and verifies that it exists
    * in the workspace.
    * @throws Exception
    */
   public void testSimpleProjectCreation() throws Exception {
      
      String testProjectName = getClass().getName() + ".SimpleProject1";
      //create the project
      this.workbenchHelper.createSimpleProject(testProjectName);
      //verify it exists
      AssertionHelper.assertProjectExists(testProjectName);   
      
   }


But every time I run the test I get an IllegalStateException:
Code: Select all
java.lang.IllegalStateException: Display has not been initialized.
   at com.windowtester.runtime.swt.internal.junit.ExecutionEnvironment.getDisplay(ExecutionEnvironment.java:68)
   at com.windowtester.runtime.swt.internal.junit.ExecutionEnvironment.createUI(ExecutionEnvironment.java:92)
   at com.windowtester.runtime.swt.internal.junit.ExecutionEnvironment.getUI(ExecutionEnvironment.java:80)
   at com.windowtester.runtime.swt.internal.junit.SWTExecutionMonitor.getUI(SWTExecutionMonitor.java:232)
   at com.windowtester.runtime.swt.internal.junit.SWTExecutionContext.getUI(SWTExecutionContext.java:36)
   at com.windowtester.runtime.common.UITestCaseCommon.getUI(UITestCaseCommon.java:268)
   at XXX.test.helpers.WorkbenchHelper.createSimpleProject(WorkbenchHelper.java:36)
   at XXX.test.MyTest.testSimpleProjectCreation(MyTest.java:50)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
   at java.lang.reflect.Method.invoke(Method.java:597)
   at junit.framework.TestCase.runTest(TestCase.java:164)
   at junit.framework.TestCase.runBare(TestCase.java:130)
   at com.windowtester.runtime.common.UITestCaseCommon.access$001(UITestCaseCommon.java:26)
   at com.windowtester.runtime.common.UITestCaseCommon$1.run(UITestCaseCommon.java:133)
   at com.windowtester.runtime.common.UITestCaseCommon$2.run(UITestCaseCommon.java:150)
   at com.windowtester.internal.runtime.junit.core.SequenceRunner$1.run(SequenceRunner.java:46)


The line 36 in WorkbenchHelper is the line with the getUi() call. Is it possible to write helpers in an extra file? How? If I make the context static I dont have a method "click". Should I pass the ui as parameter?

Michael
mwa
 
Posts: 24
Joined: Mon Jun 16, 2008 4:38 am

Re: UI Testing Talk at EclipseCon08

Postby mwa » Mon Jun 16, 2008 11:37 pm

The implementation idea is from this example:
http://downloads.instantiations.com/WindowTesterDoc/integration/latest/docs/download/com.windowtester.swt.samples3.2_new_api.zip
From the file EclipseTest.java. I will now take an IUIContext as parameter and make the methods static. I´ll post my results here.
I propose the following structure
Code: Select all
package.test
package.test.helpers
package.test.assertions
package.test.conditions

The payoff is now, that the requirements can be met fully in the test package, programmed by the tester or QA. The developers are able to provide the right helpers, assertions and conditions in the other packages. Stubs can be given by the tester.
Perhaps we can devide the helpers in gui helpers (createProject,...) and other helpers (application specific, like parsing xml etc.).
Whats your opinion?

Michael

Michael
mwa
 
Posts: 24
Joined: Mon Jun 16, 2008 4:38 am

Re: UI Testing Talk at EclipseCon08

Postby Phil Quitslund » Tue Jun 17, 2008 8:21 am

Another alternative is to have the UIContext instance be a field in the helper. In other words, something like this:

Code: Select all
class Helper {
   private final IUIContext ui;
   public Helper(IUIContext ui) {
      this.ui = ui;
   }
   ...
}


This has the advantage of a convenient pointer to a uicontext instance. On the downside, and this is a principle reason I prefer statics, is that accessing helper methods through a helper instance requires us to keep an instance around which, in my opinion, is cumbersome. That is, I prefer:

Code: Select all
class Test ... {
   public void testA() throws Exception() {
     createProject(getUI(), "MyProject");
     ...
   }
}


over:

Code: Select all
import static Helper.*;
class Test ... {
   public void testA() throws Exception() {
     getHelper().createProject("MyProject");
     ...
   }
   Helper getHelper() {
      return new Helper(getUI());
   }
}


But it's really 6 of one and a half dozen of the other... (And in fact, the more I look at it, I may like the second pattern more after all! Hmmm... The good news is it's easy to switch down the line if one structure seems better than the other. One thing that might help the decision is to think about whether inheritance --- and specifically overriding behavior --- would be of use in factoring Helpers. At the moment, I'm not sure. But I look forward to finding out! :) )

As for your proposed structure, it looks good. As I said before, if you have specific helper needs, post them here (or on a separate thread) or send them to support. After our Ganymede release ships, we'll return to the task of making our existing helpers more available.

Thanks!

-phil
--
Phil Quitslund
Software Engineer
Google, Inc.
Phil Quitslund
Moderator
 
Posts: 491
Joined: Fri Apr 28, 2006 6:26 am

Re: UI Testing Talk at EclipseCon08

Postby mwa » Mon Jun 23, 2008 6:09 am

Hello again,

I am now implementing my WorkbenchHelper. Here is my current work:
Code: Select all

import com.xxx.helpers.conditions.ConditionHelper;
import com.windowtester.runtime.IUIContext;
import com.windowtester.runtime.swt.condition.shell.ShellDisposedCondition;
import com.windowtester.runtime.swt.condition.shell.ShellShowingCondition;
import com.windowtester.runtime.swt.locator.ButtonLocator;
import com.windowtester.runtime.swt.locator.MenuItemLocator;
import com.windowtester.runtime.swt.locator.TableItemLocator;
import com.windowtester.runtime.swt.locator.TreeItemLocator;
import com.windowtester.runtime.swt.locator.eclipse.ViewLocator;
import java.util.*;

/**
* Contains some static helper methods for the graphical user interface.
*/
public final class WorkbenchHelper {
   /**
    * Only static methods. Strategy class.
    */
   private WorkbenchHelper(){
   }
   
   /**
    * Calls createSimpleProject with respective name.
    *
    * @see createSimpleProject
    * @param projectName The name of the project to create.
    */
   public static void createProject(IUIContext ui, String projectName) throws Exception {
      createSimpleProject(ui, projectName);
   }
   
   /**
    * Create a simple project with the given name.
    */
   public static void createSimpleProject(IUIContext ui, String projectName) throws Exception {
      
      ui.click(new MenuItemLocator("&File/&New\tAlt+Shift+N/P&roject..."));
      ui.wait(new ShellShowingCondition("New Project"));
      ui.click(new TreeItemLocator("General/Project"));
      ui.click(new ButtonLocator("&Next >"));
      ui.enterText(projectName);
      ui.click(new ButtonLocator("&Finish"));
      ui.wait(new ShellDisposedCondition("project.creation.dialog"));
      ConditionHelper.waitForProject(ui, projectName);
   }
   
   
   /**
    * Create a Java project with the given name.
    */
   public static void createJavaProject(IUIContext ui, String projectName) throws Exception {
      ui.click(new MenuItemLocator("&File/&New\tAlt+Shift+N/P&roject..."));
      ui.wait(new ShellShowingCondition("New Project"));
      ui.click(new TreeItemLocator("Java/Java Project"));
      ui.click(new ButtonLocator("&Next >"));
      ui.enterText(projectName);
      ui.click(new ButtonLocator("&Finish"));
      ui.wait(new ShellDisposedCondition("project.creation.dialog"));
      ConditionHelper.waitForProject(ui, projectName);
   }
   
   /**
    * Deletes a project by selection it in the tree navigation and confirms deletion.
    * @param ui The actual Interface.
    * @param projectName
    * @throws Exception
    */
   public static void deleteProject(IUIContext ui, String projectName) throws Exception {
   }
   
   /**
    * Proves if in the current selected perspective the given views are visible and clickable.
    * @param viewNames The Names of the views that should be visible an clickable.
    * @return The views which are found and are visible to the user.
    */
   public static Vector<String> viewsAreShown(Vector<String> viewNames){
      Vector<String> foundViews = new Vector<String>();
      for(Enumeration<String> e = viewNames.elements();e.hasMoreElements();){
         String viewName = (String) e.nextElement();
         // if the view with the given name exists and is shown add to result
                                                // how can we know???
         foundViews.addElement(viewName);
      }
      return foundViews;
   }
   
   /**
    * Opens a file with the default editor.
    * @param ui The actual Interface.
    * @param fileNameAndPath The file name and the path in the project explorer.
    * @throws Exception
    */
   public static void openFile(IUIContext ui, String fileNameAndPath, String nameOfTreeView) throws Exception {
      ui.click(2, new TreeItemLocator(fileNameAndPath, new ViewLocator(nameOfTreeView)));
   }
   
   /**
    * Opens the perspective with the given name.
    * @param ui
    * @param perspectiveName
    * @throws Exception
    */
   public static void openPerspective(IUIContext ui, String perspectiveName) throws Exception {
      ui.click(new MenuItemLocator("Window/Open Perspective/Other..."));
      ui.wait(new ShellShowingCondition("Open Perspective"));
      ui.click(new TableItemLocator(perspectiveName));
      ui.click(new ButtonLocator("OK"));
      ui.wait(new ShellDisposedCondition("Open Perspective"));
   }
   
}


Perhaps this is useful for someone.

I could need help with implementing the method "viewsAreShown". The purpose is to verify the correct views are shown, when a perspective is clicked. If another perspective is clicked that views must not be visible any more. Because I cannot record this I have no clue at the moment. Should I pass the complete package names? And then? How do I know that the user can see this view? Thanks for help.

Michael
mwa
 
Posts: 24
Joined: Mon Jun 16, 2008 4:38 am

Re: UI Testing Talk at EclipseCon08

Postby Phil Quitslund » Mon Jun 23, 2008 8:37 am

Thanks for sharing your progress!

If you use the view id, you should be able to test view visibility like this:

Code: Select all
if (new ViewLocator(viewId).isVisible())
  ...


I'm looking forward to getting our helpers out for you to play with. It will be great to compare approaches.
--
Phil Quitslund
Software Engineer
Google, Inc.
Phil Quitslund
Moderator
 
Posts: 491
Joined: Fri Apr 28, 2006 6:26 am

Re: UI Testing Talk at EclipseCon08

Postby mwa » Tue Jun 24, 2008 7:14 am

Thanks! That works now (with small change:
Code: Select all
if (new ViewLocator(viewId).isVisible().testUI(ui)){

)

Actually I try to implement a method which is closing a file without saving changes:
Code: Select all
   
public static void closeFile(IUIContext ui, String fileName)
         throws Exception{
      
      ui.click(new MenuItemLocator("File/Close"));
      if(/* something was edited */) {
         ui.wait(new ShellDisposedCondition("Progress Information"));
         ui.wait(new ShellShowingCondition("Save Resource"));
         ui.click(new ButtonLocator("&No"));
         ui.wait(new ShellDisposedCondition("Save Resource"));
      }
      
}

My problem is now, if nothing was changed at all I´ll get a failure. What if could I insert to prevent the test from failing?

A second question would be: can I close the file without the menu? If two files are opened and the wrong is active the menu entry "close" would be false. The XYLocator can be used like this:
Code: Select all
ui.click(new XYLocator(new CTabItemLocator("file.XML"), 18, 11));

But how do I know the coordinates when the files name varies?

Perhaps this is the wrong way to do this since I do not want to test eclipses functionality. I want to test a special plugin. Is this a general issue with those helpers? I think I will implement a class EclipseHelpers for e.g. closing a file without the need for mouse movements.

Michael
mwa
 
Posts: 24
Joined: Mon Jun 16, 2008 4:38 am

Re: UI Testing Talk at EclipseCon08

Postby Phil Quitslund » Tue Jun 24, 2008 7:52 am

The issue with the editor can be addressed by saving all unsaved changes. I do this in a tearDown of a base class:

Code: Select all
   
   protected void tearDown() throws Exception {
      saveAllIfNecessary();
   }
   protected void saveAllIfNecessary() throws WidgetSearchException {
      if (anyUnsavedChanges())
         getUI().click(new MenuItemLocator("File/Save All"));
   }

   private boolean anyUnsavedChanges() {
      return new DirtyEditorCondition().test();
   }


Where the condition is implemented as follows:

Code: Select all
public class DirtyEditorCondition implements ICondition {

      
   /**
    * Determine if any open editors are "dirty".
    * WARNING! This method MUST be called on the UI thread,
    *
    * @return <code>true</code> if at least one editor has unsaved changes, else
    *         <code>false</code>
    */
   protected static boolean anyUnsavedChanges0() {
      IWorkbenchWindow[] windows = PlatformUI.getWorkbench().getWorkbenchWindows();
      for (int i = 0; i < windows.length; i++) {
         IWorkbenchPage[] pages = windows[i].getPages();
         for (int j = 0; j < pages.length; j++) {
            IEditorReference[] editorRefs = pages[j].getEditorReferences();
            for (int k = 0; k < editorRefs.length; k++) {
               IEditorReference each = editorRefs[k];
               if (each.isDirty())
                  return true;
            }
         }
      }
      return false;
   }

   
   /**
    * Determine if any open editors are "dirty".
    *
    * @return <code>true</code> if at least one editor has unsaved changes, else
    *         <code>false</code>
    * @see com.windowtester.runtime.condition.ICondition#test()
    */
   public boolean test() {
      final boolean result[] = new boolean[] { false };
      Display.getDefault().syncExec(new Runnable() {
         public void run() {
            result[0] = anyUnsavedChanges0();
         }
      });
      return result[0];
   }
}


As for closing the file without the editor, I'm sure there is a way to do this programatically, but I don't know just how off-hand. That said, perhaps you no longer need to with the snippets above?

Either way, let me know!

-phil
--
Phil Quitslund
Software Engineer
Google, Inc.
Phil Quitslund
Moderator
 
Posts: 491
Joined: Fri Apr 28, 2006 6:26 am

Re: UI Testing Talk at EclipseCon08

Postby mwa » Thu Jun 26, 2008 6:42 am

Thanks for the help. It is working now in the way I wanted.

While thinking about it, my opinion is - though asking here - it is not something for programming with WindowTester but EclipseProgramming.
Therefore I have moved the method anyUnsavedChanges into my EclipseHelpers class. Still the condition is helpful. So I changed the code to:
Code: Select all
               result[0] = EclipseHelper.anyUnsavedChanges();

In my WorkbenchHelper the closing method is now:
Code: Select all
         
   /**
    * Closes the actual file. If there are any changes they are not saved.
    *
    * @param ui The actual UI context.
    * @throws Exception
    */
   public static void closeFile(IUIContext ui) throws Exception{
      
      final boolean result[] = new boolean[] { false };
      Display.getDefault().syncExec(new Runnable() {
         public void run() {
            result[0] = EclipseHelper.anyUnsavedChanges();
         }
      });
      ui.click(new MenuItemLocator("File/Close"));
      if(result[0]){
         ui.wait(new ShellDisposedCondition("Progress Information"));
         ui.wait(new ShellShowingCondition("Save Resource"));
         ui.click(new ButtonLocator("&No"));
         ui.wait(new ShellDisposedCondition("Save Resource"));
      }
      
   }

Of course that is special for my test of editing XML files. I dont want to save anything.

Michael
mwa
 
Posts: 24
Joined: Mon Jun 16, 2008 4:38 am

Re: UI Testing Talk at EclipseCon08

Postby Phil Quitslund » Thu Jun 26, 2008 8:28 am

Hey Michael,

Totally agreed: that's more Eclipse/RCP related functionality. You're right in moving it to another helper.

Thanks again for sharing your progress.
--
Phil Quitslund
Software Engineer
Google, Inc.
Phil Quitslund
Moderator
 
Posts: 491
Joined: Fri Apr 28, 2006 6:26 am


Return to Window Tester

Who is online

Users browsing this forum: No registered users and 1 guest