David Sills's muse is fueled by Bach, Beethoven, and Brahms (and Berlioz and Boulez). He has no life, which comports well with the über-geekitude to which he aspires. He reads and writes fitfully in English, Spanish, French, German, Italian (mostly medieval), C, VB, Perl.... Oh, and Java and XSLT, lots and lots of Java and XSLT. Trained somewhere in darkest Central America, he works today at DataSource, Inc., on the product team for the CASE tool Abri. David has posted 9 posts at DZone. View Full User Profile

Selenium and Section 508

01.01.2009
| 19460 views |
  • submit to reddit
Alt attributes (round 2)

As we said above, the requirement for alt attributes on image tags comes with an exception: where an image alt description would be redundant, use "alt=''". The sort of image envisioned here might, for example, depict a paginating arrow. Even for such images, the alt attribute can be useful unless there is already equivalent text right next to the image. (Of course, if there is such text already, why are you using the image? Use a link instead!)

For the latter case, the solution is necessarily more complex. Selenium cannot be expected to read your mind: you must know what you expect your page to look like. However, it is not difficult to determine whether it does in fact look like what you expect.

We will implement our first Selenium extension to test for this. The Selenium IDE allows you to define a set of extensions in a JavaScript file. By convention, this is called "user-extensions.js", but it can be called anything you like. More than one file can be applied if desired, enabling modularization if it is appropriate for your situation. Apply the files you want using "Options->Selenium Core Extensions" in the "Options" menu in the Selenium IDE.

Our extension, doStoreXPathInfo, is shown below. Selenium will ignore the "do" part of the function name; the command will appear in the Selenium IDE as "storeXPathInfo". It takes two arguments: the XPath expression to be evaluated and the variable within which to store the information retrieved about each node selected by the XPath expression. This variable may be used later in the same Selenium test.

The XPath expression may return elements or attributes, and may return a single node or multiple nodes. The information retrieved depends upon the node: for attributes, the value is retrieved; for elements, the inner HTML is retrieved. If none of these has succeeded, the id attribute is retrieved; failing even this, the name attribute of the node is retrieved. None of these guarantees that information is retrieved; if no information is retrieved about a node, the string "no info" is substituted. Multiple retrieved values are comma-separated.

The function deletes older copies of the variable. For convenient comparison, if no nodes are retrieved, the function stores an empty string as the variable value.

Selenium.prototype.doStoreXPathInfo = function(xpath, variableName)
{
delete storedVars[variableName];
var bbot = this.browserbot;
var result = eval_xpath(xpath, bbot.getDocument(), {
ignoreAttributesWithoutValue: bbot.ignoreAttributesWithoutValue,
allowNativeXpath: bbot.allowNativeXpath,
xpathLibrary: bbot.xpathLibrary,
namespaceResolver: bbot._namespaceResolver
});
if (result.length == 0)
{
this.doEcho('No nodes were found corresponding to the XPath expression ' + xpath);
this.doStore('', variableName);
return;
}
var values = '';
for (var i = 0; i < result.length; ++i)
{
if (values)
{
values += ',';
}
var value = '';
var attributesToBeTried = new Array('value', 'innerHTML', 'id', 'name');
for (var j = 0; j < attributesToBeTried.length && ! value; j++)
{
value = result[i].wrappedJSObject[attributesToBeTried[j]];
}
values += (value ? value : 'no info');
}
if (values)
{
this.doStore(values, variableName);
}
else
{
this.doEcho('No information was retrieved to store to the variable ' + variableName);
}
}
With this addition, the test is not too complicated. First, there should be no images without alt attributes at all:
  <tr>
<td>verifyXpathCount</td>
<td>//img[not(@alt)]</td>
<td>0</td>
</tr>
Second, you need to ensure that the correct images have empty alt attributes. You could simply count the number of such images and compare it to what you expect:
  <tr>
<td>verifyXpathCount</td>
<td>//img[@alt and not(normalize-space(@alt))]</td>
<td>yourCountHere</td>
</tr>

Of course, you would have to replace "yourCountHere" with the number of images you expect to have empty alt attributes.

However, as you are a responsible developer, you will want to check the source URLs to ensure that only the correct images have empty alt attributes. You, therefore, will use the new command instead:

  <tr>
<td>storeXPathInfo</td>
<td>//img[@alt and not(normalize-space(@alt))]/@src</td>
<td>noAltImages</td>
</tr>
<tr>
<td>verifyEval</td>
<td>'${noAltImages}'=='yourValueHere'</td>
<td>exact:true</td>
</tr>

Replace "yourValueHere" with the value you expect the page to return, that is, the source URLs of the images without the alt attribute, in document order and comma-separated (don't add extra spaces!). If you don't expect to have images without the alt attribute, you may, as noted above, use an empty string (e.g., "'${noAltImages}'==''"), but better yet is not to use this test at all; the test under Alt attribute (round 1) is quite sufficient.

Font size

Font sizes and other sizes that may be changed to assist readers on Section-508-compliant sites should be denominated in ems rather than pixels or points. This enables browser controls to be used to resize the actual appearance of fonts.

The term "em" originally referred, in early typography, to the height of the metal base from which the cast letter arose (it takes its name from the fact that in older typefaces the capital M was frequently made exactly this width). The term has been abstracted to mean "the number of points specifying a size for a particular typeface", so for a 9-point typeface, 1 em would equal 9 points. However, the em scales when the font size is changed by the user, unlike pixels or points, which are fixed in size.

While Selenium may not be the ideal testing tool for this (a simple text search can discover whether there are any appearances of "px" or "pt" in your CSS files), it can still be useful as a backstop. The test requires a somewhat more complex Selenium extension than we saw earlier:

Selenium.prototype.getFontsInPixelsCount = function()
{
var errorList = new Array();
var index = 0;
var errors = 0;
var regex = /[0-9]+[ ]*pt|[0-9]+[ ]*px/i;
var stylesheets = this.browserbot.getDocument().styleSheets;
var propertiesToBeTested = new Array('font', 'fontSize', 'letterSpacing', 'lineHeight', 'textIndent', 'wordSpacing');
for (var i = 0; i < stylesheets.length; i++)
{
var stylesheet = stylesheets[i];
if (! stylesheet.disabled && (! stylesheet.href || stylesheet.href.substring(0, 9) != 'chrome://'))
{
var rules = this.getAllStyleRules(stylesheet.cssRules);
for (var j = 0; j < rules.length; j++)
{
if (rules[j].type == CSSRule.STYLE_RULE)
{
var style = rules[j].style;
for (var k = 0; k < propertiesToBeTested.length; k++)
{
var text = style[propertiesToBeTested[k]];
if (text && regex.test(text))
{
errorList[index++] = (stylesheet.href ? stylesheet.href : 'inline-style') +
'@' + rules[j].selectorText;
errors++;
}
}
}
}
}
}
if (errors)
{
var errorStr = '';
for (var i = 0; i < errorList.length; i++)
{
if (errorStr)
{
errorStr += ',';
}
errorStr += errorList[i];
}
this.doEcho('errors: ' + errorStr);
}
return errors;
}

Selenium.prototype.getAllStyleRules = function(rules)
{
var result = new Array();
var index = 0;
for (var i = 0; i < rules.length; i++)
{
if (rules[i].type == CSSRule.STYLE_RULE)
{
result[index++] = rules[i];
}
else if (rules[i].type == CSSRule.IMPORT_RULE)
{
var inner = this.getAllStyleRules(rules[i].styleSheet.cssRules);
result = result.concat(inner);
index = result.length;
}
}
return result;
}
This extension ignores the default style sheets supplied with Firefox, since these must use point-defined fonts. Again, with this extension in place, the test is quite simple:
  <tr>
<td>verifyFontsInPixelsCount</td>
<td>0</td>
<td></td>
</tr>

Note that, like our previous extension, Selenium does not directly use the name of the extension function. Because this is a "getXXX" function, Selenium automatically generates the command "verifyFontsInPixelsCount" (also "assertFontsInPixelsCount" and "waitForFontsInPixelsCount"). It also automatically takes care of doing the comparison, marking the command as failed if the count doesn't match for the verify and assert commands (which differ in that the verify will continue after failure, while the assert will stop), or waiting for the configured maximum time period for the waitFor command (not terribly useful in this particular case).

Conclusion

This review doesn't by any means fully exhaust Section 508 requirements. There are many other important features that are needed on accessible websites. However, Selenium can be used to test the features covered here easily and there are few if any downsides to accommodating them. There is also no reason not to test new or existing sites for these features or not to add them where it is possible to do so.

Making all your sites available to as wide an audience as possible is easy with Selenium!

 

Published at DZone with permission of its author, David Sills.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Mostly Harmless replied on Thu, 2009/01/01 - 6:07pm

It is still ridiculous to me that Selenium uses an HTML file with tables rather than a proper XML file. In 2003 or 4 a friend and I sent them a patch to support XML. They passed on it. Too bad.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.