? patches
Index: drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/drupal_web_test_case.php,v
retrieving revision 1.2.2.3.2.53
diff -u -p -r1.2.2.3.2.53 drupal_web_test_case.php
--- drupal_web_test_case.php	12 Nov 2010 23:58:34 -0000	1.2.2.3.2.53
+++ drupal_web_test_case.php	13 Nov 2010 00:51:02 -0000
@@ -31,7 +31,7 @@ abstract class DrupalTestCase {
   /**
    * Time limit for the test.
    */
-  protected $timeLimit = 240;
+  protected $timeLimit = 500;
 
   /**
    * Current results of this test case.
@@ -508,45 +508,41 @@ abstract class DrupalTestCase {
    * is not restricted.
    *
    * @param $length
-   *   Length of random string to generate which will be appended to $db_prefix.
+   *   Length of random string to generate.
    * @return
    *   Randomly generated string.
    */
   public static function randomString($length = 8) {
-    global $db_prefix;
-
     $str = '';
     for ($i = 0; $i < $length; $i++) {
       $str .= chr(mt_rand(32, 126));
     }
-    return str_replace('simpletest', 's', $db_prefix) . $str;
+    return $str;
   }
 
   /**
    * Generates a random string containing letters and numbers.
    *
-   * The letters may be upper or lower case. This method is better for
-   * restricted inputs that do not accept certain characters. For example,
-   * when testing input fields that require machine readable values (ie without
-   * spaces and non-standard characters) this method is best.
+   * The string will always start with a letter. The letters may be upper or
+   * lower case. This method is better for restricted inputs that do not
+   * accept certain characters. For example, when testing input fields that
+   * require machine readable values (i.e. without spaces and non-standard
+   * characters) this method is best.
    *
    * @param $length
-   *   Length of random string to generate which will be appended to $db_prefix.
+   *   Length of random string to generate.
    * @return
    *   Randomly generated string.
    */
   public static function randomName($length = 8) {
-    global $db_prefix;
-
     $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
     $max = count($values) - 1;
-    $str = '';
-    for ($i = 0; $i < $length; $i++) {
+    $str = chr(mt_rand(97, 122));
+    for ($i = 1; $i < $length; $i++) {
       $str .= chr($values[mt_rand(0, $max)]);
     }
-    return str_replace('simpletest', 's', $db_prefix) . $str;
+    return $str;
   }
-
 }
 
 /**
@@ -644,6 +640,13 @@ class DrupalWebTestCase extends DrupalTe
   protected $plainTextContent;
 
   /**
+   * The value of the Drupal.settings JavaScript variable for the page currently loaded in the internal browser.
+   *
+   * @var Array
+   */
+  protected $drupalSettings;
+
+  /**
    * The parsed version of the page.
    *
    * @var SimpleXMLElement
@@ -790,9 +793,7 @@ class DrupalWebTestCase extends DrupalTe
   protected function drupalCreateContentType($settings = array()) {
     // Find a non-existent random type name.
     do {
-//      $name = strtolower($this->randomName(8));
-//    } while (node_type_get_type($name));
-      $name = strtolower($this->randomName(3, 'type_'));
+      $name = strtolower($this->randomName(8));
     } while (node_get_types('type', $name));
 
     // Populate defaults array.
@@ -801,7 +802,7 @@ class DrupalWebTestCase extends DrupalTe
       'name' => $name,
       'description' => '',
       'help' => '',
-      'min_word_count' => 0, // Drupal 6.
+      'min_word_count' => 0,
       'title_label' => 'Title',
       'body_label' => 'Body',
       'has_title' => 1,
@@ -854,10 +855,8 @@ class DrupalWebTestCase extends DrupalTe
       // If size is set then remove any files that are not of that size.
       if ($size !== NULL) {
         foreach ($files as $file) {
-//          $stats = stat($file->uri);
           $stats = stat($file->filename);
           if ($stats['size'] != $size) {
-//            unset($files[$file->uri]);
             unset($files[$file->filename]);
           }
         }
@@ -966,11 +965,9 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE or FALSE depending on whether the permissions are valid.
    */
   protected function checkPermissions(array $permissions, $reset = FALSE) {
-//    $available = &drupal_static(__FUNCTION__);
     static $available;
 
     if (!isset($available) || $reset) {
-//      $available = array_keys(module_invoke_all('permission'));
       $available = module_invoke_all('perm');
     }
 
@@ -1342,6 +1339,9 @@ class DrupalWebTestCase extends DrupalTe
    *   An associative array of cURL options to set, where the keys are constants
    *   defined by the cURL library. For a list of valid options, see
    *   http://www.php.net/manual/function.curl-setopt.php
+   * @param $redirect
+   *   FALSE if this is an initial request, TRUE if this request is the result
+   *   of a redirect.
    *
    * @return
    *   The content returned from the call to curl_exec().
@@ -1465,7 +1465,8 @@ class DrupalWebTestCase extends DrupalTe
     if (!$this->elements) {
       // DOM can load HTML soup. But, HTML soup can throw warnings, suppress
       // them.
-      @$htmlDom = DOMDocument::loadHTML($this->drupalGetContent());
+      $htmlDom = new DOMDocument();
+      @$htmlDom->loadHTML($this->drupalGetContent());
       if ($htmlDom) {
         $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser'));
         // It's much easier to work with simplexml than DOM, luckily enough
@@ -1512,6 +1513,13 @@ class DrupalWebTestCase extends DrupalTe
     return $out;
   }
 
+  ///**
+  // * Retrieve a Drupal path or an absolute path and JSON decode the result.
+  // */
+  //protected function drupalGetAJAX($path, array $options = array(), array $headers = array()) {
+  //  return drupal_json_decode($this->drupalGet($path, $options, $headers));
+  //}
+
   /**
    * Execute a POST request on a Drupal page.
    * It will be done as usual POST request with SimpleBrowser.
@@ -1539,25 +1547,53 @@ class DrupalWebTestCase extends DrupalTe
    *
    *   Multiple select fields can be set using name[] and setting each of the
    *   possible values. Example:
+   *   @code
    *   $edit = array();
    *   $edit['name[]'] = array('value1', 'value2');
+   *   @endcode
    * @param $submit
-   *   Value of the submit button.
+   *   Value of the submit button whose click is to be emulated. For example,
+   *   t('Save'). The processing of the request depends on this value. For
+   *   example, a form may have one button with the value t('Save') and another
+   *   button with the value t('Delete'), and execute different code depending
+   *   on which one is clicked.
+   *
+   *   This can also be set to NULL in order to emulate an Internet Explorer
+   *   submission of a form with a single text field, and pressing ENTER in that
+   *   textfield: under these conditions, no button information is added to the
+   *   POST data.
    * @param $options
    *   Options to be forwarded to url().
    * @param $headers
    *   An array containing additional HTTP request headers, each formatted as
    *   "name: value".
+   * @param $form_html_id
+   *   (optional) HTML ID of the form to be submitted. On some pages
+   *   there are many identical forms, so just using the value of the submit
+   *   button is not enough. For example: 'trigger-node-presave-assign-form'.
+   *   Note that this is not the Drupal $form_id, but rather the HTML ID of the
+   *   form, which is typically the same thing but with hyphens replacing the
+   *   underscores.
+   * @param $extra_post
+   *   (optional) A string of additional data to append to the POST submission.
+   *   This can be used to add POST data for which there are no HTML fields, as
+   *   is done by drupalPostAJAX(). This string is literally appended to the
+   *   POST data, so it must already be urlencoded and contain a leading "&"
+   *   (e.g., "&extra_var1=hello+world&extra_var2=you%26me").
    */
-  protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array()) {
+  protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
     $submit_matches = FALSE;
     if (isset($path)) {
-      $html = $this->drupalGet($path, $options);
+      $this->drupalGet($path, $options);
     }
     if ($this->parse()) {
       $edit_save = $edit;
       // Let's iterate over all the forms.
-      $forms = $this->xpath('//form');
+      $xpath = "//form";
+      if (!empty($form_html_id)) {
+        $xpath .= "[@id='" . $form_html_id . "']";
+      }
+      $forms = $this->xpath($xpath);
       foreach ($forms as $form) {
         // We try to set the fields of this form as specified in $edit.
         $edit = $edit_save;
@@ -1568,14 +1604,13 @@ class DrupalWebTestCase extends DrupalTe
 
         // We post only if we managed to handle every field in edit and the
         // submit button matches.
-        if (!$edit && $submit_matches) {
+        if (!$edit && ($submit_matches || !isset($submit))) {
           $post_array = $post;
           if ($upload) {
             // TODO: cURL handles file uploads for us, but the implementation
             // is broken. This is a less than elegant workaround. Alternatives
             // are being explored at #253506.
             foreach ($upload as $key => $file) {
-//              $file = drupal_realpath($file);
               $file = realpath($file);
               if ($file && is_file($file)) {
                 $post[$key] = '@' . $file;
@@ -1589,7 +1624,7 @@ class DrupalWebTestCase extends DrupalTe
               // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
               $post[$key] = urlencode($key) . '=' . urlencode($value);
             }
-            $post = implode('&', $post);
+            $post = implode('&', $post) . $extra_post;
           }
           $out = $this->curlExec(array(CURLOPT_URL => $action, CURLOPT_POST => TRUE, CURLOPT_POSTFIELDS => $post, CURLOPT_HTTPHEADER => $headers));
           // Ensure that any changes to variables in the other thread are picked up.
@@ -1610,7 +1645,9 @@ class DrupalWebTestCase extends DrupalTe
       foreach ($edit as $name => $value) {
         $this->fail(t('Failed to set field @name to @value', array('@name' => $name, '@value' => $value)));
       }
-      $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
+      if (isset($submit)) {
+        $this->assertTrue($submit_matches, t('Found the @submit button', array('@submit' => $submit)));
+      }
       $this->fail(t('Found the requested form fields at @path', array('@path' => $path)));
     }
   }
@@ -1791,7 +1828,7 @@ class DrupalWebTestCase extends DrupalTe
             break;
           case 'submit':
           case 'image':
-            if ($submit == $value) {
+            if (isset($submit) && $submit == $value) {
               $post[$name] = $value;
               $submit_matches = TRUE;
             }
@@ -1811,6 +1848,48 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
+   * Builds an XPath query.
+   *
+   * Builds an XPath query by replacing placeholders in the query by the value
+   * of the arguments.
+   *
+   * XPath 1.0 (the version supported by libxml2, the underlying XML library
+   * used by PHP) doesn't support any form of quotation. This function
+   * simplifies the building of XPath expression.
+   *
+   * @param $xpath
+   *   An XPath query, possibly with placeholders in the form ':name'.
+   * @param $args
+   *   An array of arguments with keys in the form ':name' matching the
+   *   placeholders in the query. The values may be either strings or numeric
+   *   values.
+   * @return
+   *   An XPath query with arguments replaced.
+   */
+  protected function buildXPathQuery($xpath, array $args = array()) {
+    // Replace placeholders.
+    foreach ($args as $placeholder => $value) {
+      // XPath 1.0 doesn't support a way to escape single or double quotes in a
+      // string literal. We split double quotes out of the string, and encode
+      // them separately.
+      if (is_string($value)) {
+        // Explode the text at the quote characters.
+        $parts = explode('"', $value);
+
+        // Quote the parts.
+        foreach ($parts as &$part) {
+          $part = '"' . $part . '"';
+        }
+
+        // Return the string.
+        $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0];
+      }
+      $xpath = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $xpath);
+    }
+    return $xpath;
+  }
+
+  /**
    * Perform an xpath search on the contents of the internal browser. The search
    * is relative to the root element (HTML tag normally) of the page.
    *
@@ -1821,11 +1900,14 @@ class DrupalWebTestCase extends DrupalTe
    *   format and return values see the SimpleXML documentation,
    *   http://us.php.net/manual/function.simplexml-element-xpath.php.
    */
-  protected function xpath($xpath) {
+  protected function xpath($xpath, array $arguments = array()) {
     if ($this->parse()) {
+      $xpath = $this->buildXPathQuery($xpath, $arguments);
       return $this->elements->xpath($xpath);
     }
-    return FALSE;
+    else {
+      return FALSE;
+    }
   }
 
   /**
@@ -1868,8 +1950,8 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
-    $links = $this->xpath('//a[text()="' . $label . '"]');
-    $message = ($message ?  $message : t('Link with label "!label" found.', array('!label' => $label)));
+    $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
+    $message = ($message ?  $message : t('Link with label %label found.', array('%label' => $label)));
     return $this->assert(isset($links[$index]), $message, $group);
   }
 
@@ -1888,8 +1970,48 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertNoLink($label, $message = '', $group = 'Other') {
-    $links = $this->xpath('//a[text()="' . $label . '"]');
-    $message = ($message ?  $message : t('Link with label "!label" not found.', array('!label' => $label)));
+    $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
+    $message = ($message ?  $message : t('Link with label %label not found.', array('%label' => $label)));
+    return $this->assert(empty($links), $message, $group);
+  }
+
+  /**
+   * Pass if a link containing a given href (part) is found.
+   *
+   * @param $href
+   *   The full or partial value of the 'href' attribute of the anchor tag.
+   * @param $index
+   *   Link position counting from zero.
+   * @param $message
+   *   Message to display.
+   * @param $group
+   *   The group this message belongs to, defaults to 'Other'.
+   *
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertLinkByHref($href, $index = 0, $message = '', $group = 'Other') {
+    $links = $this->xpath('//a[contains(@href, :href)]', array(':href' => $href));
+    $message = ($message ?  $message : t('Link containing href %href found.', array('%href' => $href)));
+    return $this->assert(isset($links[$index]), $message, $group);
+  }
+
+  /**
+   * Pass if a link containing a given href (part) is not found.
+   *
+   * @param $href
+   *   The full or partial value of the 'href' attribute of the anchor tag.
+   * @param $message
+   *   Message to display.
+   * @param $group
+   *   The group this message belongs to, defaults to 'Other'.
+   *
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertNoLinkByHref($href, $message = '', $group = 'Other') {
+    $links = $this->xpath('//a[contains(@href, :href)]', array(':href' => $href));
+    $message = ($message ?  $message : t('No link containing href %href found.', array('%href' => $href)));
     return $this->assert(empty($links), $message, $group);
   }
 
@@ -1910,15 +2032,15 @@ class DrupalWebTestCase extends DrupalTe
    */
   protected function clickLink($label, $index = 0) {
     $url_before = $this->getUrl();
-    $urls = $this->xpath('//a[text()="' . $label . '"]');
+    $urls = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
 
     if (isset($urls[$index])) {
       $url_target = $this->getAbsoluteUrl($urls[$index]['href']);
     }
 
-    $this->assertTrue(isset($urls[$index]), t('Clicked link "!label" (!url_target) from !url_before', array('!label' => $label, '!url_target' => $url_target, '!url_before' => $url_before)), t('Browser'));
+    $this->assertTrue(isset($urls[$index]), t('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), t('Browser'));
 
-    if (isset($urls[$index])) {
+    if (isset($url_target)) {
       return $this->drupalGet($url_target);
     }
     return FALSE;
@@ -1928,26 +2050,28 @@ class DrupalWebTestCase extends DrupalTe
    * Takes a path and returns an absolute path.
    *
    * @param $path
-   *   The path, can be a Drupal path or a site-relative path. It might have a
-   *   query, too. Can even be an absolute path which is just passed through.
+   *   A path from the internal browser content.
    * @return
-   *   An absolute path.
+   *   The $path with $base_url prepended, if necessary.
    */
   protected function getAbsoluteUrl($path) {
-    $options = array('absolute' => TRUE);
+    global $base_url, $base_path;
+
     $parts = parse_url($path);
-    // This is more crude than the menu_is_external but enough here.
     if (empty($parts['host'])) {
-      $path = $parts['path'];
-      $base_path = base_path();
-      $n = strlen($base_path);
-      if (substr($path, 0, $n) == $base_path) {
-        $path = substr($path, $n);
+      // Ensure that we have a string (and no xpath object).
+      $path = (string) $path;
+      // Strip $base_path, if existent.
+      $length = strlen($base_path);
+      if (substr($path, 0, $length) === $base_path) {
+        $path = substr($path, $length);
+      }
+      // Ensure that we have an absolute path.
+      if ($path[0] !== '/') {
+        $path = '/' . $path;
       }
-      if (isset($parts['query'])) {
-        $options['query'] = $parts['query'];
-      }
-      $path = url($path, $options);
+      // Finally, prepend the $base_url.
+      $path = $base_url . $path;
     }
     return $path;
   }
@@ -2056,6 +2180,13 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
+   * Gets the value of the Drupal.settings JavaScript variable for the currently loaded page.
+   */
+  protected function drupalGetSettings() {
+    return $this->drupalSettings;
+  }
+
+  /**
    * Gets an array containing all e-mails sent during this test case.
    *
    * @param $filter
@@ -2093,6 +2224,42 @@ class DrupalWebTestCase extends DrupalTe
     $this->url = $url;
     $this->plainTextContent = FALSE;
     $this->elements = FALSE;
+    $this->drupalSettings = array();
+    //if (preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $content, $matches)) {
+    //  $this->drupalSettings = drupal_json_decode($matches[1]);
+    //}
+  }
+
+  /**
+   * Sets the value of the Drupal.settings JavaScript variable for the currently loaded page.
+   */
+  protected function drupalSetSettings($settings) {
+    $this->drupalSettings = $settings;
+  }
+
+  /**
+   * Pass if the internal browser's URL matches the given path.
+   *
+   * @param $path
+   *   The expected system path.
+   * @param $options
+   *   (optional) Any additional options to pass for $path to url().
+   * @param $message
+   *   Message to display.
+   * @param $group
+   *   The group this message belongs to, defaults to 'Other'.
+   *
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertUrl($path, array $options = array(), $message = '', $group = 'Other') {
+    if (!$message) {
+      $message = t('Current URL is @url.', array(
+        '@url' => var_export(url($path, $options), TRUE),
+      ));
+    }
+    $options['absolute'] = TRUE;
+    return $this->assertEqual($this->getUrl(), url($path, $options), $message, $group);
   }
 
   /**
@@ -2320,7 +2487,14 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertTitle($title, $message = '', $group = 'Other') {
-    return $this->assertEqual(current($this->xpath('//title')), $title, $message, $group);
+    $actual = (string) current($this->xpath('//title'));
+    if (!$message) {
+      $message = t('Page title @actual is equal to @expected.', array(
+        '@actual' => var_export($actual, TRUE),
+        '@expected' => var_export($title, TRUE),
+      ));
+    }
+    return $this->assertEqual($actual, $title, $message, $group);
   }
 
   /**
@@ -2336,29 +2510,37 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertNoTitle($title, $message = '', $group = 'Other') {
-    return $this->assertNotEqual(current($this->xpath('//title')), $title, $message, $group);
+    $actual = (string) current($this->xpath('//title'));
+    if (!$message) {
+      $message = t('Page title @actual is not equal to @unexpected.', array(
+        '@actual' => var_export($actual, TRUE),
+        '@unexpected' => var_export($title, TRUE),
+      ));
+    }
+    return $this->assertNotEqual($actual, $title, $message, $group);
   }
 
   /**
-   * Assert that a field exists in the current page by the given XPath.
+   * Asserts that a field exists in the current page by the given XPath.
    *
    * @param $xpath
    *   XPath used to find the field.
    * @param $value
-   *   Value of the field to assert.
+   *   (optional) Value of the field to assert.
    * @param $message
-   *   Message to display.
+   *   (optional) Message to display.
    * @param $group
-   *   The group this message belongs to.
+   *   (optional) The group this message belongs to.
+   *
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertFieldByXPath($xpath, $value, $message = '', $group = 'Other') {
+  protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
     $fields = $this->xpath($xpath);
 
     // If value specified then check array for match.
     $found = TRUE;
-    if ($value) {
+    if (isset($value)) {
       $found = FALSE;
       if ($fields) {
         foreach ($fields as $field) {
@@ -2412,25 +2594,26 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
-   * Assert that a field does not exist in the current page by the given XPath.
+   * Asserts that a field does not exist in the current page by the given XPath.
    *
    * @param $xpath
    *   XPath used to find the field.
    * @param $value
-   *   Value of the field to assert.
+   *   (optional) Value of the field to assert.
    * @param $message
-   *   Message to display.
+   *   (optional) Message to display.
    * @param $group
-   *   The group this message belongs to.
+   *   (optional) The group this message belongs to.
+   *
    * @return
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoFieldByXPath($xpath, $value, $message = '', $group = 'Other') {
+  protected function assertNoFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
     $fields = $this->xpath($xpath);
 
     // If value specified then check array for match.
     $found = TRUE;
-    if ($value) {
+    if (isset($value)) {
       $found = FALSE;
       if ($fields) {
         foreach ($fields as $field) {
@@ -2444,7 +2627,7 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
-   * Assert that a field exists in the current page with the given name and value.
+   * Asserts that a field exists in the current page with the given name and value.
    *
    * @param $name
    *   Name of field to assert.
@@ -2462,7 +2645,7 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
-   * Assert that a field does not exist with the given name and value.
+   * Asserts that a field does not exist with the given name and value.
    *
    * @param $name
    *   Name of field to assert.
@@ -2480,7 +2663,7 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
-   * Assert that a field exists in the current page with the given id and value.
+   * Asserts that a field exists in the current page with the given id and value.
    *
    * @param $id
    *   Id of field to assert.
@@ -2498,7 +2681,7 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
-   * Assert that a field does not exist with the given id and value.
+   * Asserts that a field does not exist with the given id and value.
    *
    * @param $id
    *   Id of field to assert.
@@ -2516,7 +2699,7 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
-   * Assert that a checkbox field in the current page is checked.
+   * Asserts that a checkbox field in the current page is checked.
    *
    * @param $id
    *   Id of field to assert.
@@ -2526,12 +2709,12 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertFieldChecked($id, $message = '') {
-    $elements = $this->xpath('//input[@id="' . $id . '"]');
+    $elements = $this->xpath('//input[@id=:id]', array(':id' => $id));
     return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is checked.', array('@id' => $id)), t('Browser'));
   }
 
   /**
-   * Assert that a checkbox field in the current page is not checked.
+   * Asserts that a checkbox field in the current page is not checked.
    *
    * @param $id
    *   Id of field to assert.
@@ -2541,12 +2724,48 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertNoFieldChecked($id, $message = '') {
-    $elements = $this->xpath('//input[@id="' . $id . '"]');
+    $elements = $this->xpath('//input[@id=:id]', array(':id' => $id));
     return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : t('Checkbox field @id is not checked.', array('@id' => $id)), t('Browser'));
   }
 
   /**
-   * Assert that a field exists with the given name or id.
+   * Asserts that a select option in the current page is checked.
+   *
+   * @param $id
+   *   Id of select field to assert.
+   * @param $option
+   *   Option to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   *
+   * @todo $id is unusable. Replace with $name.
+   */
+  protected function assertOptionSelected($id, $option, $message = '') {
+    $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
+    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : t('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)), t('Browser'));
+  }
+
+  /**
+   * Asserts that a select option in the current page is not checked.
+   *
+   * @param $id
+   *   Id of select field to assert.
+   * @param $option
+   *   Option to assert.
+   * @param $message
+   *   Message to display.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoOptionSelected($id, $option, $message = '') {
+    $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
+    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['selected']), $message ? $message : t('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $id)), t('Browser'));
+  }
+
+  /**
+   * Asserts that a field exists with the given name or id.
    *
    * @param $field
    *   Name or id of field to assert.
@@ -2558,11 +2777,11 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertField($field, $message = '', $group = 'Other') {
-    return $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), '', $message, $group);
+    return $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL, $message, $group);
   }
 
   /**
-   * Assert that a field does not exist with the given name or id.
+   * Asserts that a field does not exist with the given name or id.
    *
    * @param $field
    *   Name or id of field to assert.
@@ -2574,7 +2793,37 @@ class DrupalWebTestCase extends DrupalTe
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertNoField($field, $message = '', $group = 'Other') {
-    return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), '', $message, $group);
+    return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL, $message, $group);
+  }
+
+  /**
+   * Asserts that each HTML ID is used for just a single element.
+   *
+   * @param $message
+   *   Message to display.
+   * @param $group
+   *   The group this message belongs to.
+   * @param $ids_to_skip
+   *   An optional array of ids to skip when checking for duplicates. It is
+   *   always a bug to have duplicate HTML IDs, so this parameter is to enable
+   *   incremental fixing of core code. Whenever a test passes this parameter,
+   *   it should add a "todo" comment above the call to this function explaining
+   *   the legacy bug that the test wishes to ignore and including a link to an
+   *   issue that is working to fix that legacy bug.
+   * @return
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertNoDuplicateIds($message = '', $group = 'Other', $ids_to_skip = array()) {
+    $status = TRUE;
+    foreach ($this->xpath('//*[@id]') as $element) {
+      $id = (string) $element['id'];
+      if (isset($seen_ids[$id]) && !in_array($id, $ids_to_skip)) {
+        $this->fail(t('The HTML ID %id is unique.', array('%id' => $id)), $group);
+        $status = FALSE;
+      }
+      $seen_ids[$id] = TRUE;
+    }
+    return $this->assert($status, $message, $group);
   }
 
   /**
@@ -2588,11 +2837,12 @@ class DrupalWebTestCase extends DrupalTe
    *   XPath for specified values.
    */
   protected function constructFieldXpath($attribute, $value) {
-    return '//textarea[@' . $attribute . '="' . $value . '"]|//input[@' . $attribute . '="' . $value . '"]|//select[@' . $attribute . '="' . $value . '"]';
+    $xpath = '//textarea[@' . $attribute . '=:value]|//input[@' . $attribute . '=:value]|//select[@' . $attribute . '=:value]';
+    return $this->buildXPathQuery($xpath, array(':value' => $value));
   }
 
   /**
-   * Assert the page responds with the specified response code.
+   * Asserts the page responds with the specified response code.
    *
    * @param $code
    *   Response code. For example 200 is a successful page request. For a list
@@ -2609,6 +2859,24 @@ class DrupalWebTestCase extends DrupalTe
   }
 
   /**
+   * Asserts the page did not return the specified response code.
+   *
+   * @param $code
+   *   Response code. For example 200 is a successful page request. For a list
+   *   of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
+   * @param $message
+   *   Message to display.
+   *
+   * @return
+   *   Assertion result.
+   */
+  protected function assertNoResponse($code, $message = '') {
+    $curl_code = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
+    $match = is_array($code) ? in_array($curl_code, $code) : $curl_code == $code;
+    return $this->assertFalse($match, $message ? $message : t('HTTP response not expected !code, actual !curl_code', array('!code' => $code, '!curl_code' => $curl_code)), t('Browser'));
+  }
+
+  /**
    * Asserts that the most recently sent e-mail message has the given value.
    *
    * The field in $name must have the content described in $value.
@@ -2692,8 +2960,11 @@ function simpletest_verbose($message, $o
     $class = $test_class;
     $verbose = variable_get('simpletest_verbose', TRUE);
     $directory = $file_directory . '/simpletest/verbose';
-//    return file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
-    return file_check_directory($directory, FILE_CREATE_DIRECTORY);
+    $writable = file_check_directory($directory, FILE_CREATE_DIRECTORY);
+    if ($writable && !file_exists($directory . '/.htaccess')) {
+      file_put_contents($directory . '/.htaccess', "<IfModule mod_expires.c>\nExpiresActive Off\n</IfModule>\n");
+    }
+    return $writable;
   }
   return FALSE;
 }
Index: simpletest.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.css,v
retrieving revision 1.1.6.5
diff -u -p -r1.1.6.5 simpletest.css
--- simpletest.css	12 Nov 2010 23:58:35 -0000	1.1.6.5
+++ simpletest.css	13 Nov 2010 00:51:02 -0000
@@ -1,28 +1,41 @@
-/* $Id: simpletest.css,v 1.1.6.5 2010/11/12 23:58:35 davereid Exp $
-
-/* Addon for the simpletest module */
-#simpletest {
-}
+/* $Id: simpletest.css,v 1.1.6.5 2010/11/12 23:58:35 davereid Exp $ */
 
 /* Test Table */
 #simpletest-form-table th.select-all {
-  width: 25px;
+  width: 1em;
 }
-
 th.simpletest_test {
-  width: 160px;
+  width: 16em;
 }
 
-table#simpletest-form-table tr td {
+.simpletest-image {
+  display: inline-block;
+  cursor: pointer;
+  width: 1em;
+}
+.simpletest-group-label label {
+  display: inline;
+  font-weight: bold;
+}
+.simpletest-test-label label {
+  margin-left: 1em; /* LTR */
+}
+.simpletest-test-description .description {
+  margin: 0;
+}
+#simpletest-form-table tr td {
   background-color: white;
   color: #494949;
 }
-
-table#simpletest-form-table tr.simpletest-group td {
+#simpletest-form-table tr.simpletest-group td {
   background-color: #EDF5FA;
   color: #494949;
 }
 
+table#simpletest-form-table tr.simpletest-group label {
+  display: inline;
+}
+
 div.message > div.item-list {
   font-weight: normal;
 }
@@ -30,46 +43,33 @@ div.message > div.item-list {
 div.simpletest-pass {
   color: #33a333;
 }
-
 .simpletest-fail {
   color: #981010;
 }
 
 tr.simpletest-pass.odd {
-  background: #b6ffb6;
+  background-color: #b6ffb6;
 }
-
 tr.simpletest-pass.even {
-  background: #9bff9b;
+  background-color: #9bff9b;
 }
-
 tr.simpletest-fail.odd {
-  background: #ffc9c9;
+  background-color: #ffc9c9;
 }
-
 tr.simpletest-fail.even {
-  background: #ffacac;
+  background-color: #ffacac;
 }
-
 tr.simpletest-exception.odd {
-  background: #f4ea71;
+  background-color: #f4ea71;
 }
-
 tr.simpletest-exception.even {
-  background: #f5e742;
+  background-color: #f5e742;
 }
-
 tr.simpletest-debug.odd {
-  background: #eeeeee;
+  background-color: #eee;
 }
-
 tr.simpletest-debug.even {
-  background: #ffffff;
-}
-
-div.simpletest-image {
-  display: inline;
-  cursor: pointer;
+  background-color: #fff;
 }
 
 a.simpletest-collapse {
@@ -77,9 +77,7 @@ a.simpletest-collapse {
   width: 0;
   top: -99em;
   position: absolute;
-
 }
-
 a.simpletest-collapse:focus,
 a.simpletest-collapse:hover {
   font-size: 80%;
Index: simpletest.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.info,v
retrieving revision 1.4.2.2.2.8
diff -u -p -r1.4.2.2.2.8 simpletest.info
--- simpletest.info	4 Jan 2010 22:38:18 -0000	1.4.2.2.2.8
+++ simpletest.info	13 Nov 2010 00:51:02 -0000
@@ -1,43 +1,15 @@
 ; $Id: simpletest.info,v 1.4.2.2.2.8 2010/01/04 22:38:18 boombatower Exp $
-; Core: Id: simpletest.info,v 1.10 2009/08/31 18:30:26 webchick Exp
-name = "SimpleTest"
-description = "Provides a framework for unit and functional testing."
-; package = Core
+name = SimpleTest
+description = Provides a framework for unit and functional testing.
 package = Development
-; version = VERSION
-; core = 7.x
 core = 6.x
-php = 5.2 ; Drupal 6
+php = 5.2
 files[] = simpletest.module
 files[] = simpletest.pages.inc
 files[] = simpletest.install
 files[] = simpletest.test
 files[] = drupal_web_test_case.php
-
-; Drupal 6
-files[] = tests/block.test
+configure = admin/build/testing/settings
 
 ; Tests in tests directory.
-; files[] = tests/actions.test
-; files[] = tests/batch.test
-; files[] = tests/bootstrap.test
-; files[] = tests/browser.test
-; files[] = tests/cache.test
-; files[] = tests/common.test
-; files[] = tests/database_test.test
-; files[] = tests/error.test
-; files[] = tests/file.test
-; files[] = tests/filetransfer.test
-; files[] = tests/form.test
-; files[] = tests/graph.test
-; files[] = tests/image.test
-; files[] = tests/lock.test
-; files[] = tests/mail.test
-; files[] = tests/menu.test
-; files[] = tests/module.test
-; files[] = tests/registry.test
-; files[] = tests/schema.test
-; files[] = tests/session.test
-; files[] = tests/theme.test
-; files[] = tests/unicode.test
-; files[] = tests/xmlrpc.test
+files[] = tests/block.test
Index: simpletest.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.install,v
retrieving revision 1.4.2.3.2.24
diff -u -p -r1.4.2.3.2.24 simpletest.install
--- simpletest.install	12 Nov 2010 23:58:35 -0000	1.4.2.3.2.24
+++ simpletest.install	13 Nov 2010 00:51:02 -0000
@@ -1,6 +1,5 @@
 <?php
 // $Id: simpletest.install,v 1.4.2.3.2.24 2010/11/12 23:58:35 davereid Exp $
-// Core: Id: simpletest.install,v 1.26 2009/08/17 19:14:41 webchick Exp
 
 /**
  * @file
@@ -124,16 +123,16 @@ function simpletest_uninstall() {
 
   // Remove generated files.
   $path = file_directory_path() . '/simpletest';
-//  $files = file_scan_directory($path, '/.*/');
   $files = file_scan_directory($path, '.*');
   foreach ($files as $file) {
-//    file_unmanaged_delete($file->uri);
     unlink($file->filename);
   }
   rmdir($path);
 }
 
 /**
+ * Implements hook_requirements().
+ *
  * Check that the cURL extension exists for PHP.
  */
 function simpletest_requirements($phase) {
Index: simpletest.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.module,v
retrieving revision 1.33.2.4.2.25
diff -u -p -r1.33.2.4.2.25 simpletest.module
--- simpletest.module	12 Nov 2010 23:58:35 -0000	1.33.2.4.2.25
+++ simpletest.module	13 Nov 2010 00:51:02 -0000
@@ -1,6 +1,5 @@
 <?php
 // $Id: simpletest.module,v 1.33.2.4.2.25 2010/11/12 23:58:35 davereid Exp $
-// Core: Id: simpletest.module,v 1.71 2009/08/24 00:14:21 webchick Exp
 
 /**
  * @file
@@ -114,15 +113,10 @@ function _simpletest_format_summary_line
  *   drupal being the default.
  */
 function simpletest_run_tests($test_list, $reporter = 'drupal') {
-  cache_clear_all();
-//  $test_id = db_insert('simpletest_test_id')
-//    ->useDefaults(array('test_id'))
-//    ->execute();
   db_query('INSERT INTO {simpletest_test_id} (test_id) VALUES (default)');
   $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
 
   // Clear out the previous verbose files.
-//  file_unmanaged_delete_recursive(file_directory_path() . '/simpletest/verbose');
   simpletest_clean_temporary_directory(file_directory_path() . '/simpletest/verbose');
 
   // Get the info for the first test being run.
@@ -176,12 +170,8 @@ function _simpletest_batch_operation($te
   // Perform the next test.
   $test_class = array_shift($test_list);
 
-  // Drupal 6.
   require_once drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
   $classes = simpletest_test_get_all_classes();
-
-//  require_once $classes[$test_class]['file'];
-  // Drupal 6.
   if (!empty($classes[$test_class]['file'])) {
     require_once $classes[$test_class]['file'];
   }
@@ -237,7 +227,7 @@ function _simpletest_batch_finished($suc
   module_invoke_all('test_group_finished');
 }
 
-/*
+/**
  * Get information about the last test that ran given a test ID.
  *
  * @param $test_id
@@ -320,7 +310,6 @@ function simpletest_log_read($test_id, $
  * @see simpletest_registry_files_alter()
  */
 function simpletest_test_get_all() {
-//  $groups = &drupal_static(__FUNCTION__);
   static $groups;
 
   if (!$groups) {
@@ -331,23 +320,16 @@ function simpletest_test_get_all() {
     }
     else {
       // Select all clases in files ending with .test.
-//      $classes = db_select('registry')
-//        ->fields('registry', array('name'))
-//        ->condition('type', 'class')
-//        ->condition('filename', '%.test', 'LIKE')
-//        ->execute();
       $classes = simpletest_test_get_all_classes();
 
-      $groups = array();
 
       // Check that each class has a getInfo() method and store the information
       // in an array keyed with the group specified in the test information.
+      $groups = array();
       foreach ($classes as $class) {
-//        $class = $class->name;
         $class = $class['class'];
-
+        // Test classes need to implement getInfo() to be valid.
         if (class_exists($class) && method_exists($class, 'getInfo')) {
-          // Valid test class, retrieve test information.
           $info = call_user_func(array($class, 'getInfo'));
 
           // If this test class requires a non-existing module, skip it.
@@ -404,29 +386,6 @@ function simpletest_test_get_all_classes
   return $classes;
 }
 
-///**
-// * Implementation of hook_registry_files_alter().
-// *
-// * Add the test files for disabled modules so that we get a list containing
-// * all the avialable tests.
-// */
-//function simpletest_registry_files_alter(&$files, $modules) {
-//  foreach ($modules as $module) {
-//    // Only add test files for disabled modules, as enabled modules should
-//    // already include any test files they provide.
-//    if (!$module->status) {
-//      $dir = $module->dir;
-//      if (!empty($module->info['files'])) {
-//        foreach ($module->info['files'] as $file) {
-//          if (substr($file, -5) == '.test') {
-//            $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight);
-//          }
-//        }
-//      }
-//    }
-//  }
-//}
-
 /**
  * Remove all temporary database tables and directories.
  */
@@ -440,6 +399,9 @@ function simpletest_clean_environment() 
   else {
     drupal_set_message(t('Clear results is disabled and the test results table will not be cleared.'), 'warning');
   }
+
+  // Detect test classes that have been added, renamed or deleted.
+  cache_clear_all('simpletest', 'cache');
 }
 
 /**
@@ -449,17 +411,18 @@ function simpletest_clean_database() {
 //  $tables = db_find_tables(Database::getConnection()->prefixTables('{simpletest}') . '%');
   $tables = simpletest_get_like_tables();
   $schema = drupal_get_schema_unprocessed('simpletest');
-  $ret = array();
+  $count = 0;
   foreach (array_diff_key($tables, $schema) as $table) {
     // Strip the prefix and skip tables without digits following "simpletest",
     // e.g. {simpletest_test_id}.
     if (preg_match('/simpletest\d+.*/', $table, $matches)) {
       db_drop_table($ret, $matches[0]);
+      $count++;
     }
   }
 
-  if (count($ret) > 0) {
-    drupal_set_message(format_plural(count($ret), 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
+  if ($count > 0) {
+    drupal_set_message(format_plural($count, 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
   }
   else {
     drupal_set_message(t('No leftover tables to remove.'));
@@ -493,14 +456,11 @@ function simpletest_get_like_tables($bas
  * Find all leftover temporary directories and remove them.
  */
 function simpletest_clean_temporary_directories() {
-//  $files = scandir('public://');
   $files = scandir(file_directory_path());
   $count = 0;
   foreach ($files as $file) {
-//    $path = 'public://' . $file;
     $path = file_directory_path() . '/' . $file;
     if (is_dir($path) && preg_match('/^simpletest\d+/', $file)) {
-//      file_unmanaged_delete_recursive($path);
       simpletest_clean_temporary_directory($path);
       $count++;
     }
Index: simpletest.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.pages.inc,v
retrieving revision 1.1.2.3
diff -u -p -r1.1.2.3 simpletest.pages.inc
--- simpletest.pages.inc	12 Nov 2010 23:58:35 -0000	1.1.2.3
+++ simpletest.pages.inc	13 Nov 2010 00:51:02 -0000
@@ -60,8 +60,8 @@ function simpletest_test_form(&$form_sta
 /**
  * Returns HTML for a test list generated by simpletest_test_form() into a table.
  *
- * @param $table Form array that represent a table.
- * @return HTML output.
+ * @param $table
+ *   Form array representing the table.
  *
  * @ingroup themeable
  */
@@ -72,17 +72,15 @@ function theme_simpletest_test_table($ta
   // Create header for test selection table.
   $header = array(
     theme('table_select_header_cell'),
-//     array('data' => t('Test'), 'class' => array('simpletest_test')),
     array('data' => t('Test'), 'class' => 'simpletest_test'),
-//     array('data' => t('Description'), 'class' => array('simpletest_description')),
     array('data' => t('Description'), 'class' => 'simpletest_description'),
   );
 
   // Define the images used to expand/collapse the test groups.
   $js = array(
     'images' => array(
-      theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
-      theme('image', 'misc/menu-expanded.png', 'Collapsed', 'Collapse'),
+      theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand') . ' <a href="#" class="simpletest-collapse">(' . t('Expand') . ')</a>',
+      theme('image', 'misc/menu-expanded.png', 'Collapse', 'Collapse') . ' <a href="#" class="simpletest-collapse">(' . t('Collapse') . ')</a>',
     ),
   );
 
@@ -106,12 +104,15 @@ function theme_simpletest_test_table($ta
 
     // Expand/collapse image and group title.
     $row[] = array(
-      'data' =>  '<div class="simpletest-image" id="simpletest-test-group-' . $test_class . '"></div>&nbsp;' .
-                 '<label for="' . $test_class . '-select-all" class="simpletest-group-label">' . $key . '</label>',
-      'style' => 'font-weight: bold;'
+      'data' => '<div class="simpletest-image" id="simpletest-test-group-' . $test_class . '"></div>' .
+        '<label for="' . $test_class . '-select-all" class="simpletest-group-label">' . $key . '</label>',
+      'class' => 'simpletest-group-label',
     );
 
-    $row[] = '&nbsp;';
+    $row[] = array(
+      'data' => '&nbsp;',
+      'class' => 'simpletest-group-description',
+    );
 
     $rows[] = array('data' => $row, 'class' => 'simpletest-group');
 
@@ -131,7 +132,7 @@ function theme_simpletest_test_table($ta
       $test = $element[$test_name];
       $row = array();
 
-      $current_js['testNames'][] = 'edit-' . $test_name;
+      $current_js['testNames'][] = $test['#id'];
 
       // Store test title and description so that checkbox won't render them.
       $title = $test['#title'];
@@ -143,9 +144,18 @@ function theme_simpletest_test_table($ta
       // Test name is used to determine what tests to run.
       $test['#name'] = $test_name;
 
-      $row[] = drupal_render($test);
-      $row[] = theme('indentation', 1) . '<label for="edit-' . $test_name . '">' . $title . '</label>';
-      $row[] = '<div class="description">' . $description . '</div>';
+      $row[] = array(
+        'data' => drupal_render($test),
+        'class' => 'simpletest-test-select',
+      );
+      $row[] = array(
+        'data' => '<label for="' . $test['#id'] . '">' . $title . '</label>',
+        'class' => 'simpletest-test-label',
+      );
+      $row[] = array(
+        'data' => '<div class="description">' . $description . '</div>',
+        'class' => 'simpletest-test-description',
+      );
 
       $rows[] = array('data' => $row, 'class' => $test_class . '-test ' . ($collapsed ? 'js-hide' : ''));
     }
@@ -196,7 +206,6 @@ function simpletest_test_form_submit($fo
   }
   if (count($tests_list) > 0) {
     $test_id = simpletest_run_tests($tests_list, 'drupal');
-    dpm($test_id);
     $form_state['redirect'] = 'admin/build/testing/results/' . $test_id;
   }
   else {
@@ -241,7 +250,7 @@ function simpletest_result_form(&$form_s
   );
 
   // Cycle through each test group.
-  $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), t('Status'));
+  $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
   $form['result']['results'] = array();
   $classes = simpletest_test_get_all_classes();
 
Index: simpletest.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/simpletest/Attic/simpletest.test,v
retrieving revision 1.1.2.20
diff -u -p -r1.1.2.20 simpletest.test
--- simpletest.test	12 Nov 2010 22:52:36 -0000	1.1.2.20
+++ simpletest.test	13 Nov 2010 00:51:02 -0000
@@ -232,7 +232,6 @@ class SimpleTestFunctionalTest extends D
           $assertion['file'] = $this->asText($row->td[2]);
           $assertion['line'] = $this->asText($row->td[3]);
           $assertion['function'] = $this->asText($row->td[4]);
-//          $ok_url = file_create_url('misc/watchdog-ok.png');
           $ok_url = base_path() . 'misc/watchdog-ok.png';
           $assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
           $results['assertions'][] = $assertion;
@@ -282,6 +281,69 @@ class SimpleTestFunctionalTest extends D
   }
 }
 
+/**
+ * Test internal testing framework browser.
+ */
+class SimpleTestBrowserTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'SimpleTest browser',
+      'description' => 'Test the internal browser of the testing framework.',
+      'group' => 'SimpleTest',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * Test DrupalWebTestCase::getAbsoluteUrl().
+   */
+  function testGetAbsoluteUrl() {
+    // Testbed runs with Clean URLs disabled, so disable it here.
+    variable_set('clean_url', 0);
+    $url = 'user/login';
+
+    $this->drupalGet($url);
+    $absolute = url($url, array('absolute' => TRUE));
+    $this->assertEqual($absolute, $this->url, t('Passed and requested URL are equal.'));
+    $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), t('Requested and returned absolute URL are equal.'));
+
+    $this->drupalPost(NULL, array(), t('Log in'));
+    $this->assertEqual($absolute, $this->url, t('Passed and requested URL are equal.'));
+    $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), t('Requested and returned absolute URL are equal.'));
+
+    $this->clickLink('Create new account');
+    $url = 'user/register';
+    $absolute = url($url, array('absolute' => TRUE));
+    $this->assertEqual($absolute, $this->url, t('Passed and requested URL are equal.'));
+    $this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), t('Requested and returned absolute URL are equal.'));
+  }
+
+  /**
+   * Tests XPath escaping.
+   */
+  function testXPathEscaping() {
+    $testpage = <<< EOF
+<html>
+<body>
+<a href="link1">A "weird" link, just to bother the dumb "XPath 1.0"</a>
+<a href="link2">A second "even more weird" link, in memory of George O'Malley</a>
+</body>
+</html>
+EOF;
+    $this->drupalSetContent($testpage);
+
+    // Matches the first link.
+    $urls = $this->xpath('//a[text()=:text]', array(':text' => 'A "weird" link, just to bother the dumb "XPath 1.0"'));
+    $this->assertEqual($urls[0]['href'], 'link1', 'Match with quotes.');
+
+    $urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley'));
+    $this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.');
+  }
+}
+
 class SimpleTestMailCaptureTestCase extends DrupalWebTestCase {
   /**
    * Implement getInfo().
@@ -314,7 +376,6 @@ class SimpleTestMailCaptureTestCase exte
     $this->assertEqual(count($captured_emails), 0, t('The captured e-mails queue is empty.'), t('E-mail'));
 
     // Send the e-mail.
-//    $response = drupal_mail_sending_system('simpletest', 'drupal_mail_test')->mail($message);
     $response = drupal_mail('simpletest', 'drupal_mail_test', $message['to'], language_default(), $message, NULL, TRUE);
 
     // Ensure that there is one e-mail in the captured e-mails array.
@@ -323,7 +384,7 @@ class SimpleTestMailCaptureTestCase exte
 
     // Assert that the e-mail was sent by iterating over the message properties
     // and ensuring that they are captured intact.
-    foreach($message as $field => $value) {
+    foreach ($message as $field => $value) {
       $this->assertMail($field, $value, t('The e-mail was sent and the value for property @field is intact.', array('@field' => $field)), t('E-mail'));
     }
 
@@ -336,7 +397,6 @@ class SimpleTestMailCaptureTestCase exte
         'to' => $this->randomName(32) . '@example.com',
         'body' => $this->randomString(512),
       );
-//      drupal_mail_sending_system('drupal_mail_test', $index)->mail($message);
       $response = drupal_mail('simpletest', 'drupal_mail_test', $message['to'], language_default(), $message, NULL, TRUE);
     }
 
@@ -354,7 +414,6 @@ class SimpleTestMailCaptureTestCase exte
 
     // Send the last e-mail again, so we can confirm that the drupalGetMails-filter
     // correctly returns all e-mails with a given property/value.
-//    drupal_mail_sending_system('drupal_mail_test', $index)->mail($message);
     $response = drupal_mail('simpletest', $index, $message['to'], language_default(), $message, NULL, TRUE);
     $captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4'));
     $this->assertEqual(count($captured_emails), 2, t('All e-mails with the same id are returned when filtering by id.'), t('E-mail'));
