Essays

15 hours ago

Monday, February 8, 2010

Using CSS Selectors Instead of XPath As the Default Locator Strategy in Selenium

We use Selenium to run in-browser acceptance tests in all our Rails apps via the Selenium on Rails plugin. With early versions of Selenium you had to use verbose and clunky XPath locators to reference DOM elements in the tests, such as:

//div[@class='content']/div[@class='sidebar']/a

For a while now, though, the cssQuery library has been integrated into Selenium Core, allowing you to use CSS selectors instead. For example, the above XPath locator could be written with CSS selectors as:

css=.content .sidebar a

In addition to being shorter, the Selenium documentation itself points out that most experienced users recommend CSS locators because they’re faster for the Javascript engine to parse, which means faster tests.

Making CSS Locators the Default

While the CSS locators themselves are great, it’s a minor annoyance of mine that they’re not the default locating strategy. As a result, every locator has to be prepended with css=. If you want to change this, apply the following patch to selenium-core/scripts/selenium-browserbot.js:

@@ -1107,7 +1107,7 @@
         if (locator.startsWith('document.')) {
             return this.locateElementByDomTraversal(locator, inDocument, inWindow);
         }
-        return this.locateElementByIdentifier(locator, inDocument, inWindow);
+        return this.locateElementByCss(locator, inDocument);
     };
 }

Minor Annoyances With CSS Locators

The only thing that I don’t like with CSS locators is that indexing specific sibling elements is more verbose—it must be done with nth-child():

.content .sidebar:nth-child(1) a

With XPath you can use a simple pair of brackets:

//div[@class='content']/div[@class='sidebar'][1]/a

It would be really nice if CSS selectors had the same bracket-style indexing syntax as XPath. (Of course, this is a limitation of CSS itself, not Selenium.)

In addition, nth-child() is a little more brittle in that it isn’t constrained to the current selection scope. For example, assume you have markup like so:

<div class="content">
  <div class="something_else">…</div>
  <div class="sidebar">…</div>
</div>

There is now a non-sidebar sibling <div> appearing before the sidebar <div>. In this case, there will be no element that matches .sidebar:nth-child(1)—the sidebar is matched by .sidebar:nth-child(2). This means that adding additional unrelated markup to your page can break your tests if you’re using CSS locators. If you were using the XPath locators in this case, the indexing of the element in question would remain constant because the index doesn’t refer to children, but to elements matching that specific XPath. (If you added additional sidebar <div>’s the XPath would break as well, but all the same it’s still less brittle.)

One final point to note is that there’s also a bug in the cssQuery library that prevents nth-child() from working correctly—it’s Selenium bug #698. Unfortunately, the patch posted on the bug does not fix the problem for me. Instead, you have to resort to suffixing any nth-child() selector with a child or sibling combinator like so:

.content .sidebar:nth-child(1) > a

Despite these minor wrinkles, I find that the CSS locators used with Selenium are less verbose in general and lead to faster running tests.

Comments

14 days ago

Monday, January 25, 2010

It’s 2010—Use Clickable Labels on Checkboxes

Returning to the UPS homepage I discussed in last week’s post Improving the UPS Homepage by Remembering Fitt’s Law, another problem concerns the checkbox. I’m really dismayed that it’s 2010 now and we therefore live in the future, but we’re still seeing sites not using <label> elements with checkboxes. And indeed, that is the case here—the ‘Remember this location’ label associated with the checkbox on this page is not clickable. This was also pointed out by Patrick McElhaney in a comment on the previous post.

This is another instance of where Fitt’s law applies—the tiny checkbox is a small target area for clicking, and so it makes sense to enlarge it by making the label clickable as a proxy for the checkbox itself. In desktop software on all major platforms (OS X, Linux, and Windows) the label of a checkbox behaves in just that way, and is always clickable.

I should note that, of course, on such a large site as UPS’s the changes I’ve been suggesting would have to be backed up by some A/B testing to ensure they achieve the desired effect and do not have any unintended consequences. Of course, too, UPS may be so big and critical to its customers that they would not abandon the site no matter the magnitude of its interface shortcomings. I would be willing to bet, though, that the changes would improve visitor satisfaction if not reduce abandonment, and any site can stand to increase the satisfaction of its users.

Stay tuned for one more critique of this page in an upcoming post!

1 Comment

27 days ago

Tuesday, January 12, 2010

Improving the UPS Homepage by Remembering Fitt’s Law

Upon navigating to the UPS homepage for the first time, you as the visitor are presented with an interstitial page asking you to choose your country and language so that you can be redirected to a localized version of the site. These interstitial pages are quite common for companies which have global operations and customers in many countries.

In the case of UPS this page is generally very understated and well-designed. One way in which it could be improved, though, is by increasing the size of the button. Fitt’s law states that the clickability of a target is a function of the target’s size. The button here is 17×18 pixels—an area of 306 pixels. That’s only 0.0236% of the screen on my 1440×900 display—two ten-thousandths of the available pixels. There’s not much else on this page taking up any space, so there’s plenty of real estate to make this change.

Critically, pages like these are barriers that potentially keep visitors from continuing further into your site. They are simply one more hurdle the user must jump over before doing what they want to do on your site. Presenting visitors with a very small (and therefore difficult to click) button is one additional thing that could lead to premature abandonment. It could be the straw that breaks the camel’s back. There’s a tradeoff between making buttons microscopically small and comically large, and I think this button could definitely be enlarged to minimize the effect of the hurdle of an interstitial page.

What do you think? What else could be improved here? Stay tuned for a couple more posts about this page.

9 Comments

2 months ago

Wednesday, December 2, 2009

Getting on Your Users’ Side

The first thing I looked for when I went to Hulu’s page for The Soup was the list of full episodes—someone had told me about a particular episode and I wanted to check it out. Scrolling down the page, though, I found only clips. My immediate reaction to discovering this fact was childish but came directly from my gut: I was pissed off. I wanted to see an episode of The Soup, I wanted to see it now, and now Hulu was standing in my way instead of making it easy for me like watching television online should be. Annoyed, I began to click away elsewhere while silently saying to myself, ‘I don’t care about their licensing predicaments and the intricacies of television broadcast rights—why can’t Hulu just get its act together and get this show so I can watch it?’

But then I noticed the little notice under the show banner.

Screenshot of the Hulu homepage for ‘The Soup’

It says, ‘Full episodes of The Soup are not available for online streaming at this time. We’ll continue to request them on our users’ behalf.‘ With those two little sentences, Hulu completely reversed the snap negative judgment I had hefted on the site. To the user, those sentences make it clear that not only is Hulu aware that this show is missing full episodes, but that they seem just as dissatisfied with the situation as I do. And what’s more, they’re working hard to change the situation.

The wording ‘on our user’s behalf’ is absolutely perfect for the intended effect. It made me feel like Hulu was on my side.

Users are unforgiving, and they’ll blame everything connected to your site on you even when those things are out of your control. So when something is out of your control, show the user you’re on their side. When you can’t give your users everything they want, commiserating goes a long way towards making them happy anyways.


Hulu goes a lot further than simply dumping boilerplate into this notice box. Many shows have descriptions of how many episodes are available for streaming at once, and when new episodes of the show will return if the show is on a hiatus.

Screenshot of the Hulu homepage for ‘Dollhouse’

Comments

7 months ago

Saturday, June 13, 2009

Fixing Maximize Bookmarklets in Safari 4

When Apple released the Safari 4 public beta at the end of February, I discovered an annoying bug: the little Javascript bookmarklet that I use to maximize the browser window didn’t work anymore. I find the behavior of Safari’s green zoom button extremely annoying. It doesn’t expand the browser window to fill the screen, but instead only resizes it to fit the content of the current page. This is typically some weird height and width that causes every subsequent page you view scroll vertically and horizontally.

Of course, Apple’s Human Interface Guidelines for Resizing and Zooming Windows explain that the zoom button isn’t actually supposed to expand the window to fill the screen, but toggle the window between a standard application-defined size and a user-defined size. Regardless of what the human interface guidelines say, though, I still don’t like what it does, and I’m apparently not the only person. There are hundreds of pages asking how to fix it.

In case you’re wondering, the bookmarklet is:

javascript:self.moveTo(0,0);self.resizeTo(screen.availWidth,screen.availHeight);

I simply put the bookmarklet first in my bookmark bar, which lets me maximize Safari by hitting ⌘1. I didn’t write this little snippet of Javascript, but I can’t remember where I found it and there are versions of it all over the Web.

So, back to the real story. Upon further investigation, I determined that the bookmarklet only works when the Safari window contains a single tab. If the window has more than one tab, the bookmarklet doesn’t work. I wasn’t the only one who noticed this bug, as I found the post Safari 4 breaks bookmarklets? on This Is the Green Room.

I figured the problem would be fixed when the final version of Safari 4 was released, but when that happened this past week, the problem remained. Googling a bit, I found WebKit bug 24218, which notes that the Javascript window.moveTo and resizeTo methods do not work in Safari windows with more than one tab.

Since the bug was marked unconfirmed and didn’t have any activity on it for months, I decided to download the WebKit source and try to track down the cause of the problem. I checked out the WebKit code and built it according to the instructions. After digging a bit and setting some breakpoints, I figured out that the following method in WebCore/page/DOMWindow.cpp implements window.moveTo in the Javascript engine:

void DOMWindow::moveTo(float x, float y) const
{
if (!m_frame)
return;

Page* page = m_frame->page();
if (!page)
return;

if (m_frame != page->mainFrame())
return;

FloatRect fr = page->chrome()->windowRect();
FloatRect sr = screenAvailableRect(page->mainFrame()->view());
fr.setLocation(sr.location());
FloatRect update = fr;
update.move(x, y);
// Security check (the spec talks about UniversalBrowserWrite to disable this check…)
adjustWindowRect(sr, fr, update);
page->chrome()->setWindowRect(fr);
}

This method basically does some adjustments with the screen and window rectangles, and then finally invokes Chrome::setWindowRect to perform the actual move of the window. Stepping through the method when clicking the bookmarklet with one and two tabs showed that all the rectangle calculations were resulting in the same values regardless of the number of tabs. The invocation of setWindowRect simply wasn’t moving the window when there was more than one tab.

After a bit of indirection through some other classes, Chrome::setWindowRect calls WebChromeClient::setWindowRect in mac/WebCoreSupport/WebChromeClient.mm:

void WebChromeClient::setWindowRect(const FloatRect& rect)
{
NSRect windowRect = toDeviceSpace(rect, [m_webView window]);
[[m_webView _UIDelegateForwarder] webView:m_webView setFrame:windowRect];
}

This method basically translates the rectangle coordinates between the scale used by the window and the WebView itself, and then sends setFrame:windowRect: to WebUIDelegate. This delegate is the bridge between WebKit’s Javascript engine and the actual Cocoa browser window (I think), and WebKit is passing it the correct coordinates to move the window. WebUIDelegate, though, simply doesn’t move the window as it should when there is more than one tab, which leads me to believe the bug is in Safari itself, not WebKit.

I updated the WebKit bug and filed a new bug for Safari on Radar. I thought for a moment that this behavior might actually be intentional, to prevent pages from resizing the browser window via Javascript when there are other open tabs that would be affected. But since the bookmarklet works in Safari 3, I tend to doubt this new behavior is by design as it would break a lot of existing Javascript. Scripts embedded in an actual page are similarly unable to move or resize the window when there’s more than one tab open, so the problem doesn’t have anything to do with the Javascript being in a bookmarklet.

14 Comments