Finding a sailboat

Finding a sailboat, especially a cruising sailboat you hope to take over the horizon someday, is a hard task.  A lot harder than you think.

I’ve spent a good deal of time the last few years (off and on, ever since coming back from Fiji in 2005) doing just that.  I admit that it hasn’t been a single-minded focus, since I’ve gotten married, started a business, moved to Ecuador (and back), had a baby (with another one on the way), had a day job, and re-immersed myself in my business during that time.

I almost bought a boat last year around Christmas time.  A Halberg-Rassey Rasmus 35 parked in Elliot Bay.  The owners were a young couple that had crossed the Pacific, so I knew the boat could do it again (with some work).  But the economy, a car wreck in the Seattle snow, and another baby on the way have intervened, and it looks like I’ll spend another Christmas without a boat.  I actually didn’t even go sailing once this year (yet — I’m open to offers).

But I still want to be involved in the sailing community.

I have a service, sailhost.org, where I will help cruisers and racers create blogs and websites, and I’m willing to donate hosting to worthy askers.

I’m also working on sales tools for sailboats.  I don’t want to be the next Yachtworld, but I want to help sailboat dealers and brokers reach their customers.

Besides doing testing, I’ve been branching out into sales automation, CRM, etc. (You might have noticed some posts about CRM tools, and some frustrated tweets about SugarCRM in particular.

Broadreach is the name I’m giving for the sales consulting and tools —  apart from One Shore, which is more geared towards testing.  I’m going to focus on reaching out to Yacht brokers, sailing charters, boat surveyers, marine finance, buyers agents and teachers such as John Neal and John Kretschmer, and other people who make money in, on, or around the water.

I’m looking for a broker willing to tell me what they want or need to make their job better.  More particularly, I’m looking at it from the potential buyer’s perspective.

Ideally, I’d like to have a resource available for sailboat buyers so they can learn the process easier.  I’d like to be able to point to books, articles, and people who could help people out.

Two names I’m thinking of are boatbuyersresource.com and sailboatbuyer.com.

I was also introduced to Lasers last year by my friend Ed.  Another venture I’m thinking about is laserlisting.com

Verification and Exploration

There’s a move towards separating what testers do into two categories, called by some “testing” and “checking”.  I don’t particularly like the terminology (or most attempts to redefine words), but I appreciate the distinction, as well as the reaching towards semantic clarity.

The distinction being that there are two unique activities that are called testing.

The first is exemplified by unit tests, but also includes the functional testing done to “assure” requirements are met — usually represented on spreadsheets with lots of steps and expected results, as well as regression testing (which those typically become.)

The second is exemplified by exploratory testing, but also includes performance testing, security penetration testing (though hopefully, a standard set of security tests falls under the first category), and usability tests.

This isn’t a clear separation, and some amount of each category can fall into any type of testing.

The distinguishing characteristic is whether the expected result of the test is known at the outset or not.  Or rather, whether the result is assumed.  The object of the result is the distinguishing factor — whether the object is to learn something new or verify something already known is correct.

This is where I find the appellations of “testing” and “checking” imprecise.  While you can clearly distinguish which category a test falls into, the labels are not obvious and must be learned – although it’s clear enough to categorize them when given the labels.

  • A test, for instance, at school is a verification of a student understanding requirements.
  • A test of strength or will is a challenge to find it’s capacity.
  • A scientific experiment can be considered a test, but isn’t typically called so
  • A check is what you do to the test results at school
  • A check is also a boundary limiting what can be done

I think better terms would be “verification” and “exploration”.  It is clear with verification that you are “checking” for expected behavior.  And it is also clear with exploration that you are “testing” unknown factors.

Both are important to QA, and being aware of the two categories, regardless of the terminology, helps you decide how the focus of testing should be balanced.  In different situations, more of one type or the other may be advantageous.  For instance, a startup with an unproven product might want to do more exploratory testing; but a mature product with clear requirements might want to do more verification.  The point being that neither should neglected.

SugarCRM, custom fields, and complex objects

LornaJane has a good write-up on using custom fields with SugarCRM.

But I have a client that needs complex fields.  For instance, email_address has the following fields:

+--------------------+--------------+------+-----+---------+-------+
| Field              | Type         | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+-------+
| id                 | char(36)     | NO   | PRI | NULL    |       |
| email_address      | varchar(255) | NO   | MUL | NULL    |       |
| email_address_caps | varchar(255) | NO   | MUL | NULL    |       |
| invalid_email      | tinyint(1)   | YES  |     | 0       |       |
| opt_out            | tinyint(1)   | YES  |     | 0       |       |
| date_created       | datetime     | YES  |     | NULL    |       |
| date_modified      | datetime     | YES  |     | NULL    |       |
| deleted            | tinyint(1)   | YES  |     | 0       |       |
+--------------------+--------------+------+-----+---------+-------+

How can you, for instance, add an opt_out field to a telephone number?

While email_addresses is it’s own table, other information such as phones and addresses are just fields in the contact (or account or lead, etc.) table.  This is a major flaw in SugarCRM.
It looks like to create an address or phone number that is a “complex object” (like an email address), or to modify the components of an email address, I’ll need to create a custom module.

Does that mean I have to throw away the functionality of the existing contacts module?

A new model for CRM (*groan*)

I’m using SugarCRM, which is nice, but seems to have a confused model for contacts, accounts, leads, etc.

I realize that this is the “standard” CRM way to describe entities, but can’t help but think it could be improved.

SugarCRM has a few oddities that make it particularly grating.  For instance:

  • Last Name is required for all Contacts
  • While Contacts are referenced by Last Name, Leads are referenced by First Name
  • I’m still not sure who should be a Lead and who should be a Contact
    (is this just a personal naming preference?)
  • What’s the difference between a Sales Lead/Contact and a Marketing Lead/Contact?
  • How can you have a contact/lead without a name?  Is there such a thing as an Account Lead?
  • How do you associate contacts with accounts (without manually updating)
  • How can you associate, for example Outlook Business Contact Manager accounts with SugarCRM accounts?
  • How can you target Campaigns at Businesses (not individuals)

I think SugarCRM’s model isn’t clear enough.

First of all, both businesses and individuals have contact information.  Most often, a business will be targeted for a campaign, become a lead, and eventually a Sales opportunity develops, where you might gain individual contact.

Second, whoops, guess I said it, businesses (or rather, organizations), are the target of campaigns, not just individuals.  Businesses and individuals have contact information.  You have relationships with businesses and individuals.

When I write my own CRM system, I’ll break the mold, and think of things in terms of Entities and Relationships (and Actions).

An entity can be an individual or an organization.

Individuals are always people (or I guess you could also have animals or plants)

Organizations have an associated type.  It can be a business, non-profit, government, informal association, or other (and you can add types.)

You have relationships with Entities.

Relationships are categorized as Business or Personal.

Personal relationships can be type Family, Friends, Acquaintence, Other. (and can be added to)

Business relationships can be type Supplier/Vendor/Manufacturer, Customer/Client, Partner, Other (with additions & subcategories.)

You can have multiple relationships with a single record (contact, account, etc.)

Accounts are Entities you do business with, not every organization you may do business with.  Because any entity (organization or individual) has contact information, and can become a lead, you don’t need to use “Accounts” to mean “organizations” and “Contacts” to mean “people”.

Entities will have contact information.  Account records can concentrate on accounting, and Contact records can concentrate on contacting.  A contact is an action.  An individual (or group) is something you have a relationship with.  Part of a relationship is contacting them.

Entities can be in both a customer bucket and a prospect bucket for a different sales opportunity or campaign.

entity_inheritance

relationship_inheritance

Automated testing is a craft

Automated testing is a craft.  It requires skill, training, and at least a bit of talent.  I guess it’s like everything else in that sense.  But it’s unlike everything else in that you are doing automated testing.  Not a lot of people do that.

There are way more people that writing code or do testing than do both.  Even including the two fringes of writing unit “tests” and record and playback “testing”.  I think (somewhat vainly) that the ability (and inclination) to automate testing on a practical, functional level (in a useful way) is an unusual commodity.

Or maybe I’m just not that talented and have chosen the career path equivalent of joining the water-polo team.  (Notice that I restrained myself from saying “lacrosse” or “soccer”.)

As a result, I think we tend to get into our silos, and each have to reinvent not only the wheel, but the concept of propulsion.  We scrape together bits and pieces from the internet, and occasionalyl find published gems like Beautiful Testing or Java Power Tools.  If we’re lucky, we make a few contacts with peers through blogs, local user groups,  or conferences and share ideas.  If we’re really lucky we get to work with and learn from a few insightful co-workers.  But let’s face it, test automation isn’t exactly good water-cooler fare.

Beautiful testing is a great book (that I hope to read someday soon).  I’d like to see something focused, not abstract, and relevant.  I don’t want to see tutorials or best practices, but individual experience so others can say “I do that too” or “I didn’t think of that.”

I want it to be focused, so that there can be enough common information to discuss details.  On the other hand, I’d like to see discussions about the pros and cons of specific tools (Selenium vs. Watir) and techniques (browser driven vs. browser simulated) and plunge into the details of design patterns (Page Objects, Helpers) and organization strategies (suite grouping, continuous integration, test environments).

I guess what I’d really like to see are case studies, real or hypothetical, that tell why certain decisions were made, how they were implemented, and what lessons were learned.  I could see either focusing specifically on web application functional testing with Selenium, or covering a variety of platforms (iPhone, web services, AJAX) and tools (Selenium, Webrat, Cucumber, SOAPUI, etc.)  I suppose a certain variety would be needed to discuss the pros and cons of each decision.

I’m not looking for rights and wrongs, “best practices”, just individual solutions (I guess I’m repeating myself).  Anyway, I’d envision something like 10 essays, with a discussion afterward among the authors, and then of course, an online forum where comments from all comers could be given.

I’d be interested in hearing from QA “celebrities”, authors, and trainers, including (but not limited to):

Tool creators (a few of many):

as well as anonymous testers in their silos who maybe haven’t written a word about what they’re doing.

Interacting with SeleniumRC directly

In my previous post, I talked about launching Selenium RC from the command line.   I’d mentioned navigating to http://localhost:4444/ after starting the Selenium Server but removed it since it wasn’t directly pertinent to a getting started post.  Here’s the removed content:

You can launch selenium-server in “interactive” mode and enter commands directly from the command interface.

C:\selenium-remote-control-1.0.1\selenium-server-1.0.1>java -jar selenium-server.jar -interactive
11:54:06.143 INFO - Java: Sun Microsystems Inc. 1.5.0_13-b05
11:54:06.145 INFO - OS: Windows Vista 6.0 x86
11:54:06.161 INFO - v1.0.1 [2696], with Core v@VERSION@ [@REVISION@]
11:54:06.300 INFO - Version Jetty/5.1.x
11:54:06.302 INFO - Started HttpContext[/,/]
11:54:06.306 INFO - Started HttpContext[/selenium-server,/selenium-server]
11:54:06.308 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]
11:54:06.338 INFO - Started SocketListener on 0.0.0.0:4444
11:54:06.340 INFO - Started org.mortbay.jetty.Server@863399
Entering interactive mode... type Selenium commands here (e.g: cmd=open&1=http://www.yahoo.com)

The format for the query string is the same as for a HTML table in Selenese:

  • What comes after “cmd=” is the first column.
  • What comes after “&1=” is the second column.
  • What comes after “&2=” is the third column (sometimes optional).

All normal commands including “open”, “type”, “click”, “verifyTextPresent”, etc. are supported, as well as special commands such getNewBrowserSession and shutDownSeleniumServer.

The first thing you need to do is start a browser session.  You can either type in the command window (when in interactive mode):

cmd=getNewBrowserSession&1=*chrome&2=http://one-shore.com
12:14:20.698 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=getNewBrowserSession&1=*chrome&2=ht
tp://one-shore.com
12:14:20.706 INFO - Command request: getNewBrowserSession[*chrome, http://one-shore.com] on session null
12:14:20.708 INFO - creating new remote session
12:14:20.710 INFO - Allocated session e4c0bda97077462aa916c61111a7f806 for http://one-shore.com, launching...
12:14:20.842 INFO - Preparing Firefox profile...
12:14:24.647 INFO - Launching Firefox...

or you can enter the url in a browser:

http://localhost:4444/selenium-server/driver/?cmd=getNewBrowserSession&1=*chrome&2=http://one-shore.com

You’ll notice that when you ran the command getNewBrowserSession, it opened two new browser windows.

The first windows is a controller window.  You can interact with this much as you would with the command line. The second window is a blank window, that will eventually be the window where your commands get executed.

The first window  is the “chrome” driver.  It is the controlling interface for selenium RC. (Chrome refers to the base version of Firefox, which has elevated privileges (and thus can avoid cross-site scripting limitations.  The equivalent for Internet Explorer is “iehta”.)

selenium_rc_chrome

There are three frames.  The first is the control frame, the second shows a command history, and the third is blank.  Depending on your configuration, the commands are executed in either the third frame, or in the second window (which is the default for newer versions of Selenium RC.)

A sessionID is returned, and will be needed to be passed as an argument to interact with selenium-server from the URL.  The command line interface is tied to the sessionID automatically.

cmd=open&1=/contact
12:36:18.783 INFO - ---> Requesting http://localhost:4444/selenium-server/driver?cmd=open&1=/contact&sessionId=bb5439b87
fec422282d2ec42a2611171
12:36:18.803 INFO - Command request: open[/contact, ] on session bb5439b87fec422282d2ec42a2611171
12:36:19.379 INFO - Got result: OK on session bb5439b87fec422282d2ec42a2611171
12:37:30.973 INFO - Command request: open[/about, ] on session bb5439b87fec422282d2ec42a2611171
12:37:31.192 INFO - Got result: OK on session bb5439b87fec422282d2ec42a2611171
12:39:25.267 INFO - Got result: ERROR: Command timed out on session 79eaade6ab7249a1ac05ac71eab60e33

or enter the following URL into any browser window.

http://localhost:4444/selenium-server/driver?cmd=open&1=/contact&sessionId=bb5439b87fec422282d2ec42a2611171

Note that you have to pass the sessionID as well.  This is the only difference.


This is the way your selenium driver interacts with the selenium server at a low level.  But you can forget all about that, because the APIs  abstract all that and you can enter commands in Java (or Ruby or PHP, etc.) that are similar to the familiar HTML commands.

For instance, the above session could be executed with the following Java commands

Selenium selenium = new DefaultSelenium("localhost", 4444, "*chrome", "http://one-shore.com/");
selenium.open("/contact")

Some commands, however, are not supported directly, or are an aggregate of multiple commands, such as verifyText which in java would look more like

verifyEquals("expected", selenium.getText(locator);

Transitioning from Selenium IDE to Remote Control

I recently wrote a blog post about writing higher level test cases using Selenium RC

https://fijiaaron.wordpress.com/2009/09/02/selenium-page-objects-site-objects-data-objects-high-level-navigation/

If you are just transitioning from using Selenium IDE, it might be a bit abstract for you.

To start with, you need to decide which platform you are targeting:

  • What programming language you plan on using?
    (Selenium supports Java, Ruby, PHP, Perl, Python, C#, and Groovy)
  • What testing framework you plan on using ?
    (dependent on the language, e.g. junit, nunit, phpUnit, testng, rspec, etc.)
  • How you want to execute your tests?
    (this can be left as a step for later, once you have a working test framework)

    • in Selenium IDE (you probably don’t need this tutorial, then)
    • via the command line (e.g. with the junit textrunner)
    • in your IDE (e.g. with a plugin for Eclipse, Netbeans, IDEA, Visual Studio, Emacs)
    • with an automated build script (ant, rake, etc.)
    • from continuous integration (cruisecontrol, hudson, etc.)

I’m going to assume Java, JUnit, and Ant.  But you could just as easily use Ruby, Rspec, and Rake, or whatever you prefer.

You can use Selenium IDE to record some example tests and then convert them to Junit test cases.  This will help you get familiar with the basic format and syntax of Selenium tests written inJava with JUnit.

selenium_ide_format_junit

Here is the output of that script from Selenium IDE:

package com.example.tests;

import com.thoughtworks.selenium.*;
import java.util.regex.Pattern;
public class OneshoreContactFormTest extends SeleneseTestCase {

   public void setUp() throws Exception {
      setUp("http://www.one-shore.com/", "*chrome");
   }

   public void testOneshoreContactForm() throws Exception {
      selenium.open("/");
      selenium.click("link=contact");
      selenium.waitForPageToLoad("30000");
      selenium.select("dept", "label=Other");
      selenium.type("name", "Aaron Evans");
      selenium.type("email", "aarone@one-shore.com");
      selenium.type("subject", "online training in selenium junit");
   }
}

Note that the test extends SeleneseTestCase, which extends the base JUnit TestCase.  There are other options, including extending TestCase yourself and handling setUp (which launches the browser), or writing your own base class.

The next step is to run your tests with Selenium Remote Control:

http://seleniumhq.org/projects/remote-control/

Selenium Remote Control  can be downloaded from:

http://release.seleniumhq.org/selenium-remote-control/1.0.1/selenium-remote-control-1.0.1-dist.zip

You can start Selenium server by executing the following command (additional lines are the output):

C:\selenium-remote-control-1.0.1\selenium-server-1.0.1>java -jar selenium-server.jar
10:21:43.631 INFO - Java: Sun Microsystems Inc. 1.5.0_13-b05
10:21:43.633 INFO - OS: Windows Vista 6.0 x86
10:21:43.648 INFO - v1.0.1 [2696], with Core v@VERSION@ [@REVISION@]
10:21:43.820 INFO - Version Jetty/5.1.x
10:21:43.823 INFO - Started HttpContext[/,/]
10:21:43.826 INFO - Started HttpContext[/selenium-server,/selenium-server]
10:21:43.828 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]
10:21:43.860 INFO - Started SocketListener on 0.0.0.0:4444
10:21:43.861 INFO - Started org.mortbay.jetty.Server@863399

There are additional parameters that can be passed, including specifying a different port (the default is 4444):

java –jar selenium-server.jar –port 4445

To see all options include the –help flag:

java –jar selenium-server.jar – help

Your test can be run by executing junit from the command line, in your IDE, or via an ant script. I’ll go over that in my next post, with additional posts covering:

  • accessing  external resources in your tests (like querying a database)
  • test abstraction and organization (like page based testing)
  • using different testing frameworks (such as Rspec, JBehave, and Cucumber)
  • testing multiple browsers with Selenium Grid
  • executing your tests as part of a continuous integration process.

Open Source and Commercial CRM solutions

I’m looking over the market at Customer Relationship Management (CRM) solutions.

First what are the aspects of a CRM?

The term is a bit vague, and fairly abused, so that it has come to mean, generally, “business applications” including those used for sales & marketing, enterprise resource management (ERP – another abused term), business contact management, order fulfillment & shipping, billing & invoicing, trouble ticketing, and even sometimes e-commerce or accounting.

Of course part of the problem is that you want all these systems to work together, so many vendors offer some or all of these features in one application or suite of applications. This is especially true of the big vendors, Oracle/Siebel, SAP, and Microsoft but also true of smaller vendors who sometimes try to shoehorn one application inside another.

The core elements of a CRM include:

  • customer & account data
  • customer issues and interactions (sales, support, etc.)
  • customer intelligence & reporting

Prospective customers are often included as well, which ties in with sales and marketing.

The big  commercial CRM solutions are, as stated above:

A big player that offers CRM software as a service (SaaS) is:

Other SaaS vendors:

There are several other commercial enterprise vendors:

Open source CRM is getting increasingly popular, though often the term “open source” is abused.  Some major open source CRM applications:

SugarCRM is the big Open Source CRM system.  It is written in PHP and uses MySQL database.  There is a basic open source edition, and non-free Professional versions.  Sugar offers hosted solutions at sugarondemand.com and a number of third party

vTiger was originally a fork of SugarCRM.  It has sense gone it’s own way, and is a good product in it’s own right.  My impression is that e-commerce companies tend to look to vTiger while more traditional businesses prefer SugarCRM.  This may have to do with the technical savvy of the the IT staff of the different types of companies, with ecommerce shops naturally having a broader IT expertise.

SplendidCRM is a .NET CRM offering written in C#  that is also open source.  Microsoft IT shops, or companies that integrate with other .NET and Microsoft products (i.e. SQL Server) should investigate this.

OpenCRX is written in Java, supports many databases, and has a featureful API.  For JEE performance, it’s a good solution.

CiviCRM is based on Drupal, the PHP content management system (CMS), and targets nonprofits and associated volunteer and donor networks and compaigns.

Additional Open Source applications that offer CMS features:

Smaller commerical CRM offerings & SaaS:

I’m looking to add to this list.

My best Engrish for Chopping Block!

My wife picked up one of these at the dollar store:
The products will not be warped forever.
The products will not be warped forever.

It says:

USE WEARPROOF AND CORROSION-
RESISTING PLASTIC.  MORE CLURABLE
THAN THE WOOD ONES.
MORE HARD AND USE MORE LONG.
EASILY WASH AFTER USE.
THE PRODUCTS WILL NOT BE WAR-
PED FOREVER.
  

Definitely a candidate for www.engrish.com

Selenium Page Objects + Site Objects, Data Objects & High Level Navigation

Page Objects are gaining popularity when writing Selenium tests, and I’m glad to see people advocating for, and teaching them.  But I think that’s just the start.

With a page object you can have tests that look like this:

testChangePassword() {
  browser.open("http://one-shore.com/login");
  verifyTextPresent("Enter your username and password");
  LoginPage.login("bob", "secret");
  verifyTextPresent("Welcome, Robert");
  HomePage.goTotPreferences();
  PrefsPage.changePassword("secret", "moresecret");
  verifyTextPresent("Password changed");
}

You get a high level object that knows how to:

  1. find elements on the page
  2. perform aggregate actions

If your page object also returns the copy of the current expected page, you can do things like this:

homePage = loginPage.login(username, password);
prefsPage = homePage.goToPreferences();
prefPage.changePassword(oldpassword, newpassword);

or

page = new LoginPage();
page.login(username, password);
page.goToPreferences();
page.changePassword(oldPassword, newPassword);

or even:

landingPage.goToLoginPage().login(username, password).goToPreferences().changePassword(oldPassword, newPassword);

though I wouldn’t  recommend it.

While that’s a useful pattern, I prefer to keep my page objects  “dumb” (and even static) and build navigation into a higher level “Site Object”.  Pages can still perform in-page actions, but the Site Object is responsible for navigating, and can handle multi-page aggregate actions.

Because alot can happen, I also combine data into smarter objects.  So you can do things like this:

user bob = getRegisteredUser();
Site.changePassword(bob, newPassword);
verifyTextPresent("Password changed for" + bob.firstName);

Behind the scenes, my LoginPage class might look like this:

class LoginPage extends OneShorePage
{
  // standard fields (e.g. header, footer, navigation) are inherited for all pages on the site

  public static String url = baseUrl + "/login";
  public static String usernameField = "//div[@class='loginform']/table/tr/td[2]/input[@type='text']";
  public static String passwordField = "//div[@class='loginform']/table/tr[1]/td[2]/input[@type='password']";
  public static String loginButton = "//input[@type='submit' and @value='login']";

  public static void login(username, password)
  {
    browser.type(usernameField, username);
    browser.type(passwordField, password);
    browser.click(loginButton);
  }
}

and my Site would contain instances of all the pages, and know how to navigate between them:

class MySite {
  public static LoginPage;
  public static HomePage;
  public static PrefsPage;

  public changePassword(user, newPassword);
  {
    browser.open(LoginPage.url);
    verifyTextPresent("Enter your username and password");
    browser.type(LoginPage.usernameField, user.username);
    browser.type(LoginPage.passwordField, user.password);
    browser.click(LoginPage.loginButton);
    verifyTextPresent("Welcome, " + user.firstname);
    browser.click(HomePage.PreferencesLink);
    // this is actually a really nasty javascript popup, but I don't care, someone else wrote the Prefs page logic
    browser.click(PrefsPage.changePasswordLink);
    browser.type(PrefsPage.oldPasswordField, user.password);
    browser.type(PrefsPage.newPasswordField, newPassword);
    browser.click(PrefsPage.save);
  }
}

But of course, we can do better than that,:

  public changePasswordRefactored(user, newPassword)
  {
     Site.login(user.username, user.password);
     Site.navigateToPreferences();
     Site.changePassword(user.password, newPassword);
  }

  //...

  public login(username, password)
  {
    browser.open(Site.HomePage.url);
    browser.click(Site.HomePage.loginLink);
    verifyTextPresent("Enter your username and password");
    browser.type(Site.LoginPage.usernameInput, username);
    browser.type(Site.LoginPage.passwordInput, password);
    browser.click(Site.LoginPage.submitButton);

    try {
      verifyTextPresent("Welcome, " + username);
    }
    catch (Exception e) {
      return LoginPage;
    }

    return HomePage;
  }
}

of course, it could also use smarter page objects:

public changePreferences()
{
   LoginPage.open();
   LoginPage.enterUserName(username);
   LoginPage.enterPassWord(password);
   LoginPage.clickLogin();

   // or even just LoginPage.login(username, password);
}

Now my test looks like this (regardless of the implemenation details):

testChangePassword() {

 // these three lines would of course be refactored into setup
 User bob = getRegisteredUser(...);
 Selenium browser = new Selenium(...);
 Site.attachBrowser(browser);

 Site.changePassword(user, newPassword);
 verifyTextPresent("Password changed");
}