Archive

Archive for the ‘maven’ Category

Running cucumber on JRuby+Maven

October 29, 2008 7 comments

Cucumber is an interesting offshoot of the popular BDD tool Rspec developed by Aslak Hellesoy. Cucumber executes plain-text feature specifications against code implementing those features and verifies it. Take a look at the cucumber examples to understand what this means in practice.

This post explains how you can configure it to write specifications for a Java project using Maven and JRuby.

1. Add JRuby as a dependency in your POM:

<project>
    ...
   <dependencies>
       <dependency>
           <groupId>org.jruby</groupId>
           <artifactId>jruby-complete</artifactId>
           <version>1.1.4</version>
       </dependency>
   </dependencies>
   ...
</project>

2. Configure the exec-maven-plugin to run cucumber during integration-test phase.

<project>
...
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>1.1</version>
        <executions>
            <execution>
                <id>run-cucumber</id>
                <phase>integration-test</phase>
                <goals>
                    <goal>java</goal>
                </goals>
                <configuration>
                    <mainClass>org.jruby.Main</mainClass>
                    <arguments>
                        <argument>-S</argument>
                        <argument>rake</argument>
                        <argument>features</argument>
                    </arguments>
                </configuration>
            </execution>
        </executions>
    </plugin>
...
</project>

3. Configure a profile to install cucumber if it is not already locally available:

<project>
...
    <profiles>
        <profile>
            <id>first.time</id>
            <activation>
                <file>
                    <missing>${user.home}/.jruby/lib/ruby/gems/1.8/gems/cucumber-0.1.8</missing>
                </file>
            </activation>
            
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.codehaus.mojo</groupId>
                        <artifactId>exec-maven-plugin</artifactId>
                        <version>1.1</version>
                        <executions>
                            <execution>
                                <id>install-diff-lcs-gem</id>
                                <phase>initialize</phase>
                                <goals>
                                    <goal>java</goal>
                                </goals>
                                <configuration>
                                    <mainClass>org.jruby.Main</mainClass>
                                    <arguments>
                                        <argument>-S</argument>
                                        <argument>gem</argument>
                                        <argument>install</argument>
                                        <argument>diff-lcs</argument>
                                        <argument>--no-ri</argument>
                                        <argument>--no-rdoc</argument>
                                        <argument>--no-test</argument>
                                    </arguments>
                                </configuration>
                            </execution>
                            <execution>
                                <id>install-hoe-gem</id>
                                <phase>initialize</phase>
                                <goals>
                                    <goal>java</goal>
                                </goals>
                                <configuration>
                                    <mainClass>org.jruby.Main</mainClass>
                                    <arguments>
                                        <argument>-S</argument>
                                        <argument>gem</argument>
                                        <argument>install</argument>
                                        <argument>hoe</argument>
                                        <argument>--no-ri</argument>
                                        <argument>--no-rdoc</argument>
                                        <argument>--no-test</argument>
                                    </arguments>
                                </configuration>
                            </execution>
                            <execution>
                                <id>install-cucumber-gem</id>
                                <phase>initialize</phase>
                                <goals>
                                    <goal>java</goal>
                                </goals>
                                <configuration>
                                    <mainClass>org.jruby.Main</mainClass>
                                    <arguments>
                                        <argument>-S</argument>
                                        <argument>gem</argument>
                                        <argument>install</argument>
                                        <argument>cucumber</argument>
                                        <argument>--no-ri</argument>
                                        <argument>--no-rdoc</argument>
                                        <argument>--no-test</argument>
                                    </arguments>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
...
</project>

4. Create a file named Rakefile as a sibling of your pom.xml, with the following contents:

require 'cucumber/rake/task'
Cucumber::Rake::Task.new(:features) do |t|
  t.cucumber_opts = "--format pretty"
end

5. Running

mvn integration-test

should run cucumber now on your project. Cucumber looks for feature specifications to run in the features directory.

Please see this maven project for a test project configured with cucumber.

Categories: jruby, maven, ruby, tech

Corrupted Local Maven Repository

January 12, 2007 Leave a comment

Yesterday I got the following error when trying to run my development server using Maven:

[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Internal error in the plugin manager executing goal 
'org.apache.maven.plugins:maven-jetty-plugin:6.1.-SNAPSHOT:run': Unable to find
the mojo 'org.apache.maven.plugins:maven-war-plugin:6.1-SNAPSHOT:run' in the
plugin 'org.apache.maven.plugins:maven-jetty-plugin'

Googling around, initially, did not provide much help. But eventually I came across this post on ServiceMix users list, and my problem was similar to it. I worked around the issue by:

  1. deleting $MAVEN_REPO/org/apache/maven/plugins
  2. deleting all folders matching *jetty* in $MAVEN_REPO
Categories: maven, tech

Taking Screenshots with Selenium

December 22, 2006 16 comments

I had written about using Selenium, TestNG, Cargo and Maven to write an automated smoke test suite. It would be useful if we could get a screenshot of the application under test at every stage of the test – the screenshots would be useful to diagnose test failures. In this post I describe a crude screenshotting mechanism that I have developed to take screenshots for tests running in Selenium.

Note: The code used for this post can be downloaded here. Due to WordPress restrictions, I had to rename it with extension .jpg. Kindly rename it to .tar.gz. Also, the code is a continuation from my previous post.

The basic idea is to extend com.thoughtworks.selenium.DefaultSelenium class such that it takes a screenshot after each significant step.


public class ScreenshottingSelenium extends DefaultSelenium {
    // ..

    @Override
    public void click(String locator) {
        super.click(locator);
        takeAScreenShotOfTheApp();
    }

    @Override
    public void open(String url) {
        super.open(url);
        takeAScreenShotOfTheApp();
    }

    @Override
    public void type(String locator, String value) {
        super.type(locator, value);
        takeAScreenShotOfTheApp();
    }

    // ..
}

To take a screenshot, we need to make sure that the application window is the in the foreground and is maximized, and then take a screen capture.

To take a screen capture, we use the java.awt.Robot class:


public void takeAScreenShotOfTheApp() {
    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
    Rectangle screenBounds = new Rectangle(0, 0, screenDim.width, screenDim.height);

    Robot robot = new Robot();
    BufferedImage image =  robot.createScreenCapture(screenBounds);

    File screenshotFile = new File("target" + File.separator
                                   + "image" + System.currentTimeMillis() + ".png");
    ImageIO.write(image, "png", screenshotFile);
}

By default, Selenium shows the application under test in a browser frame. This frame, of course, cannot be maximized to occupy the entire screen. If Selenium Remote Control server is launched in multiWindow mode, the application under test is launched in a seperate browser window. To launch Selenium Remote Control server in multiWindow mode:


@BeforeSuite
public void startSeleniumServer() throws Exception {
    seleniumServer = new SeleniumServer(4444, false, true);
    seleniumServer.start();
}

To make sure that the application under test is shown in a maximized browser window:


public class ScreenshottingSelenium extends DefaultSelenium {
    // ..

    @Override
    public void start() {
        super.start();
        windowMaximize("");
    }

    // ...
}

To make sure that the application browser is on the foreground just before taking the
screenshot:


public class ScreenshottingSelenium extends DefaultSelenium {
    // ..

    @Override
    public void click(String locator) {
        windowFocus("");
        super.click(locator);
        takeAScreenShotOfTheApp();
    }

    // ...
}

Now, if we use ScreenshottingSelenium instead of DefaultSelenium, we would get screenshots of the application in the target directory. But we would like to go one step further – we want to generate a nice HTML report of the screenshots. For this we create a Reporter class:


public class Reporter {
    void startTest() {
        // Called when a test is started; creates the directory
        // for saving screenshot images of this test
    }

    void endTest() {
        // Called when a test ends; creates an HTML summary
        // of all screenshot images of this test
    }

    void recordStep(String description) {
        // Creates a screenshot and saves the image to a file
    }

    public void close() {
        // Called after all tests are run; creates an HTML summary
        // of all tests run
    }
}

The implementation of startTest(), endTest(), and close() are quite simple that I do not describe them here. recordStep is essentially the same as takeAScreenShotOfTheApp() earlier. The HTML reports are generated using couple of FreeMarker templates.

The ScreenshottingSelenium would be configured with a Reporter object; the ScreenshottingSelenium calls methods on the Reporter object at appropriate times. The startTest() method is called from the start() method. The start() method opens a browser window and this we assume signals the start of a test. The endTest() method is called from the stop() method. The stop() method closes the browser window, which we assume signals the end of a test. Method like click(), type(), open() etc call recordStep() method of the reporter object with a text description of the action just performed.


public class ScreenshottingSelenium extends DefaultSelenium {
    private Reporter reporter;

    public ScreenshottingSelenium(CommandProcessor processor, Reporter reporter) {
        super(processor);
        this.reporter = reporter;
    }

    public ScreenshottingSelenium(String serverHost, int serverPort,
            String browserStartCommand, String browserURL, Reporter reporter) {
        super(serverHost, serverPort, browserStartCommand, browserURL);
        this.reporter = reporter;
    }

    @Override
    public void start() {
        reporter.startTest();
        super.start();
        windowMaximize("");
    }

    @Override
    public void stop() {
        super.stop();
        reporter.endTest();
    }

    @Override
    public void click(String locator) {
        windowFocus("");
        super.click(locator);
        reporter.recordStep("Clicked");
    }

    @Override
    public void open(String url) {
        windowFocus("");
        super.open(url);
        reporter.recordStep("Opened " + url);
    }

    @Override
    public void type(String locator, String value) {
        windowFocus("");
        super.type(locator, value);
        reporter.recordStep("Typed '" + value + "'");
    }

    @Override
    public void waitForPageToLoad(String timeout) {
        windowFocus("");
        super.waitForPageToLoad(timeout);
        reporter.recordStep("Page loaded");
    }
}

The Reporter object needs to be created before the testsuite starts execution. Also, the close() method on the reporter object should be called after all tests in the suite are executed. We make use to @BeforeSuite and @AfterSuite TestNG annotations for this purpose:


public class SeleniumSetup {
    private static Reporter reporter;

    @BeforeSuite
    public void setupReporter() throws Exception {
    	reporter = new Reporter(new File("target" + File.separator + "screenshots"));
    }

    @AfterSuite
    public void cleanupReporter() throws Exception {
    	reporter.close();
    }

    public static Reporter getReporter() {
    	return reporter;
    }
}

//..

public class FirstTest {
    private Selenium selenium;

    @BeforeMethod
    @Parameters({ "browser" })
    public void openBrowser(String browser) {
        selenium = new ScreenshottingSelenium("localhost", 4444,
                                              "*"  + browser, "http://localhost:8080/",
                                              SeleniumSetup.getReporter());
        selenium.start();
    }

    // ..
}

To see these ideas in action:
1. Download this code bundle. Rename the .jpg file to selenium-screenshots.tar.gz.
2. The bundle contains a small web application and its smoke testsuite. Uncompress the archive to some place on your disk.
3. Install the web application on your local Maven repository.


> cd selenium-screenshots
selenium-screenshots> mvn install

4. Run the integration testsuite:


selenium-screenshots> cd functest
functest> mvn integration-test

This will download Tomcat from the web, install Tomcat server, deploy our web application onto the server, open Firefox and IE browsers and test drives the web application on the browsers. The screenshots from these would be generated in the functest/target/screenshots directory.

Acknowledgements: This technique was inspired by a forum post by Dan Fabulich.

Update: I fixed a small bug in the instructions given to run the code bundle – thanks to davidm for pointing it out in the comments. I have also updated the code bundle with the URL pointing to a newer version of Tomcat. Some versions of Selenium have trouble launching some versions of Firefox. Hence, I have commented out the FireFox tests; instead the tests run on IE.

Categories: maven, selenium, tech

Stamping QA builds with SVN revision number

December 16, 2006 3 comments

In the project I am working on now, we push newer builds to QA frequently. This is the first project here that uses SVN; we were using CVS before this one. When using CVS we usually TAG the repository before pushing a build to QA; when QA file bugs the TAG number of the build is mentioned. This helps tracking bugs effectively.

SVN puts a unique revision number to every change to the repository – every change to the repository is implictly tagged by the revision number of that changeset. So a better scheme would be to stamp the build with the SVN revision number.

The maven-buildnumber plugin is designed to do exactly this. But unfortunately the plugin is no longer maintained and does not work well. So I devised my own mechanism to get the SVN revision number. Firstly, on my continuous integration build machine (where the QA builds are cut), I installed the svn native client. Then, I added the following section in the POM:


<profiles>
  <profile>
    <id>qabuild</id>
    <build>
      <plugins>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>exec-maven-plugin</artifactId>
          <executions>
            <execution>
              <phase>compile</phase>
              <goals>
                <goal>exec</goal>
              </goals>
            </execution>
          </executions>
          <configuration>
          <!--
            We are essentially running:
                svn info > target/classes/revision.txt
            The obvious gotchas is that it works only if SVN client is
            installed on the build machine. For now, we can live with
            this restriction. A more complete plugin
            is available at
            http://commons.ucalgary.ca/projects/maven-buildnumber-plugin/howto.html
            but it is not maintained right now.
          -->
            <executable>${svn.executable}</executable>
            <arguments>
              <argument>info</argument>
              <argument>></argument>
              <argument>target/classes/revision.txt</argument>
            </arguments>
          </configuration>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

In the settings.xml file of the build machine, I set the svn.executable property to the location where the svn native client is installed. When the build machine builds out my web application it runs Maven with the ‘qabuild’ profile turned on (mvn -Pqabuild package). When this profile is turned on, the build (after the main classes are compiled) runs the native svn client and outputs the repository version information into a file in the classes directory. This file also gets packaged as part of my web application. In my web application, I read this file and include the revision number as part of the footer in each JSP. If the file cannot be found (i.e. the build was not run with qabuild profile) or if there is some error reading the file my JSP ignores those errors and prints no revision number – that way the developers are not forced to install native SVN client on their local machine (most of us use Smart SVN, which depends on the Java SVN client from tmate.org).

We use the revision number in our bug reports. We plan to enhance our bug tracking tool to have queries based on the revision number field.

Categories: maven, svn, tech