diff --git a/includes/ajax.inc b/includes/ajax.inc
index f059209..b6b8ef6 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -384,7 +384,16 @@ function ajax_get_form() {
 function ajax_form_callback() {
   list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
   drupal_process_form($form['#form_id'], $form, $form_state);
+  ajax_form_return_commands($form, $form_state, $commands);
+}
 
+/**
+ * Return a set of AJAX commands for updating a form.
+ *
+ * @see ajax_form_callback()
+ * @see drupal_get_form()
+ */
+function ajax_form_return_commands($form, $form_state, $commands = array()) {
   // We need to return the part of the form (or some other content) that needs
   // to be re-rendered so the browser can update the page with changed content.
   // Since this is the generic menu callback used by many Ajax elements, it is
@@ -642,7 +651,10 @@ function ajax_footer() {
  */
 function ajax_process_form($element, &$form_state) {
   $element = ajax_pre_render_element($element);
-  if (!empty($element['#ajax_processed'])) {
+  // Custom callback paths set in $element['#ajax']['path'] are assumed to
+  // require form caching. Using $element['#ajax']['callback'] on the other hand
+  // does not require the form to be cached, and is the preferred method.
+  if (!empty($element['#ajax_processed']) && !isset($element['#ajax']['callback'])) {
     $form_state['cache'] = TRUE;
   }
   return $element;
@@ -740,7 +752,7 @@ function ajax_pre_render_element($element) {
 
     // Assign default settings.
     $settings += array(
-      'path' => 'system/ajax',
+      'path' => $_GET['q'],
       'options' => array(),
     );
 
diff --git a/includes/form.inc b/includes/form.inc
index 130775f..b4c4f24 100644
--- a/includes/form.inc
+++ b/includes/form.inc
@@ -128,7 +128,30 @@ function drupal_get_form($form_id) {
   array_shift($args);
   $form_state['build_info']['args'] = $args;
 
-  return drupal_build_form($form_id, $form_state);
+  // Disable redirection if the request was made via AJAX.
+  $ajax_post = isset($_POST['ajax_page_state']) && $_POST['form_id'] === $form_id;
+  if ($ajax_post) {
+    $form_state['no_redirect'] = TRUE;
+    $form_state['rebuild_info']['copy']['#build_id'] = TRUE;
+    $form_state['rebuild_info']['copy']['#action'] = TRUE;
+  }
+
+  $form = drupal_build_form($form_id, $form_state);
+
+  // In the event that this form was submitted via AJAX, we need to return a
+  // set of AJAX commands to update the original form. Generate the JSON
+  // response and end the request.
+  if ($ajax_post && $form_state['process_input']) {
+    $commands = array();
+    if (isset($form['#build_id_old']) && $form['#build_id_old'] !== $form['#build_id']) {
+      $commands[] = ajax_command_update_build_id($form);
+    }
+    $commands = ajax_form_return_commands($form, $form_state, $commands);
+    ajax_deliver($commands);
+    exit();
+  }
+
+  return $form;
 }
 
 /**
@@ -409,6 +432,7 @@ function form_state_defaults() {
     'build_info' => array(
       'args' => array(),
       'files' => array(),
+      'from_cache' => FALSE,
     ),
     'temporary' => array(),
     'submitted' => FALSE,
@@ -464,6 +488,15 @@ function form_state_defaults() {
 function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
   $form = drupal_retrieve_form($form_id, $form_state);
 
+  $copy_build_id = !empty($form_state['rebuild_info']['copy']['#build_id']);
+
+  // If this form was not built from cache but needs to maintain the same build
+  // ID, copy the value from input onto the old form.
+  if (empty($form_state['build_info']['from_cache']) && isset($form_state['input']['form_build_id'])) {
+    $old_form['#build_id'] = $form_state['input']['form_build_id'];
+    $copy_build_id = FALSE;
+  }
+
   // If only parts of the form will be returned to the browser (e.g., Ajax or
   // RIA clients), or if the form already had a new build ID regenerated when it
   // was retrieved from the form cache, reuse the existing #build_id.
@@ -471,7 +504,7 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) {
   // build's data in the form cache; also allowing the user to go back to an
   // earlier build, make changes, and re-submit.
   // @see drupal_prepare_form()
-  $enforce_old_build_id = isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id']);
+  $enforce_old_build_id = isset($old_form['#build_id']) && $copy_build_id;
   $old_form_is_mutable_copy = isset($old_form['#build_id_old']);
   if ($enforce_old_build_id || $old_form_is_mutable_copy) {
     $form['#build_id'] = $old_form['#build_id'];
@@ -524,6 +557,9 @@ function form_get_cache($form_build_id, &$form_state) {
         // Re-populate $form_state for subsequent rebuilds.
         $form_state = $cached->data + $form_state;
 
+        // Indicate this build was loaded from the cache, not a fresh build.
+        $form_state['build_info']['from_cache'] = TRUE;
+
         // If the original form is contained in include files, load the files.
         // @see form_load_include()
         $form_state['build_info'] += array('files' => array());
diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc
index d592381..0d5a138 100644
--- a/modules/file/file.field.inc
+++ b/modules/file/file.field.inc
@@ -662,12 +662,10 @@ function file_field_widget_process($element, &$form_state, $form) {
   // file, the entire group of file fields is updated together.
   if ($field['cardinality'] != 1) {
     $parents = array_slice($element['#array_parents'], 0, -1);
-    $new_path = 'file/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
     $field_element = drupal_array_get_nested_value($form, $parents);
     $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
     foreach (element_children($element) as $key) {
       if (isset($element[$key]['#ajax'])) {
-        $element[$key]['#ajax']['path'] = $new_path;
         $element[$key]['#ajax']['wrapper'] = $new_wrapper;
       }
     }
diff --git a/modules/file/file.module b/modules/file/file.module
index bf7b07d..c3ba19a 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -233,6 +233,13 @@ function file_file_download($uri, $field_type = 'file') {
  * form processing is properly encapsulated in the widget element the form
  * should rebuild correctly using FAPI without the need for additional callbacks
  * or processing.
+ *
+ * This function is deprecated. This menu handler is no longer used by
+ * File module itself, but it exists for compatibility with contributed
+ * modules. Modules should now replace any use of $element['#ajax']['path']
+ * with $element['#ajax']['callback'] = 'file_managed_file_ajax'.
+ *
+ * @see file_managed_file_ajax()
  */
 function file_ajax_upload() {
   $form_parents = func_get_args();
@@ -369,7 +376,7 @@ function file_managed_file_process($element, &$form_state, $form) {
   $element['#tree'] = TRUE;
 
   $ajax_settings = array(
-    'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
+    'callback' => 'file_managed_file_ajax',
     'wrapper' => $original_id . '-ajax-wrapper',
     'effect' => 'fade',
     'progress' => array(
@@ -700,6 +707,42 @@ function file_managed_file_save_upload($element) {
 }
 
 /**
+ * AJAX callback for managed file elements.
+ */
+function file_managed_file_ajax($form, $form_state) {
+  $button = $form_state['triggering_element'];
+
+  // Get the list of parents, minus the button pressed and field delta.
+  $parents = $button['#array_parents'];
+  $button_key = array_pop($parents); // Button pressed.
+  $delta_key = array_pop($parents); // The delta for the pressed button.
+
+  // Drill down to the element being updated.
+  $element = $form;
+  foreach ($parents as $parent_key) {
+    $element = $element[$parent_key];
+  }
+
+  // Add the special Ajax class if a new file was added.
+  if (isset($element['#file_upload_delta']) && $delta_key < $element['#file_upload_delta']) {
+    $element[$delta_key]['#attributes']['class'][] = 'ajax-new-content';
+  }
+  // Otherwise just add the new content class on a placeholder.
+  else {
+    $element[$delta_key][$button_key]['#suffix'] = '<span class="ajax-new-content"></span>';
+  }
+
+  $output = drupal_render($element);
+  $commands = array();
+  $commands[] = ajax_command_replace('#' . $button['#ajax']['wrapper'], $output);
+  $commands[] = ajax_command_prepend(NULL, theme('status_messages'));
+  return array(
+    '#type' => 'ajax',
+    '#commands' => $commands,
+  );
+}
+
+/**
  * Returns HTML for a managed file element.
  *
  * @param $variables
diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test
index 1510a69..7ffd298 100644
--- a/modules/file/tests/file.test
+++ b/modules/file/tests/file.test
@@ -373,6 +373,8 @@ class FileTaxonomyTermTestCase extends DrupalWebTestCase {
  *   that aren't related to fields into it.
  */
 class FileManagedFileElementTestCase extends FileFieldTestCase {
+  protected $profile = 'minimal';
+
   public static function getInfo() {
     return array(
       'name' => 'Managed file element test',
diff --git a/modules/poll/poll.module b/modules/poll/poll.module
index 336e445..a04e770 100644
--- a/modules/poll/poll.module
+++ b/modules/poll/poll.module
@@ -366,7 +366,7 @@ function poll_form($node, &$form_state) {
 function poll_more_choices_submit($form, &$form_state) {
   // If this is a Ajax POST, add 1, otherwise add 5 more choices to the form.
   if ($form_state['values']['poll_more']) {
-    $n = $_GET['q'] == 'system/ajax' ? 1 : 5;
+    $n = isset($_POST['ajax_page_state']) ? 1 : 5;
     $form_state['choice_count'] = count($form_state['values']['choice']) + $n;
   }
   // Renumber the choices. This invalidates the corresponding key/value
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 08452f3..8f19fd3 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -2017,7 +2017,7 @@ class DrupalWebTestCase extends DrupalTestCase {
    *   case, this value needs to be an array with the following keys:
    *   - path: A path to submit the form values to for Ajax-specific processing,
    *     which is likely different than the $path parameter used for retrieving
-   *     the initial form. Defaults to 'system/ajax'.
+   *     the initial form. Defaults to the current path.
    *   - triggering_element: If the value for the 'path' key is 'system/ajax' or
    *     another generic Ajax processing path, this needs to be set to the name
    *     of the element. If the name doesn't identify the element uniquely, then
@@ -2044,15 +2044,26 @@ class DrupalWebTestCase extends DrupalTestCase {
    *   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.
+   *   (optional) An array 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").
+   *   is done by drupalPostAJAX().
    */
-  protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
+  protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = array()) {
     $submit_matches = FALSE;
     $ajax = is_array($submit);
+
+    // Backwards-compatibility, convert $extra_post string to an array.
+    if (is_string($extra_post)) {
+      $extra_post_array = explode('&', $extra_post);
+      $extra_post = array();
+      foreach ($extra_post_array as $value) {
+        if ($value) {
+          list($key, $value) = explode('=', $value);
+          $extra_post[urldecode($key)] = urldecode($value);
+        }
+      }
+    }
+
     if (isset($path)) {
       $this->drupalGet($path, $options);
     }
@@ -2070,9 +2081,12 @@ class DrupalWebTestCase extends DrupalTestCase {
         $post = array();
         $upload = array();
         $submit_matches = $this->handleForm($post, $edit, $upload, $ajax ? NULL : $submit, $form);
+        $post += $extra_post;
         $action = isset($form['action']) ? $this->getAbsoluteUrl((string) $form['action']) : $this->getUrl();
         if ($ajax) {
-          $action = $this->getAbsoluteUrl(!empty($submit['path']) ? $submit['path'] : 'system/ajax');
+          if ($submit['path']) {
+            $action = $this->getAbsoluteUrl($submit['path']);
+          }
           // Ajax callbacks verify the triggering element if necessary, so while
           // we may eventually want extra code that verifies it in the
           // handleForm() function, it's not currently a requirement.
@@ -2082,7 +2096,6 @@ class DrupalWebTestCase extends DrupalTestCase {
         // We post only if we managed to handle every field in edit and the
         // submit button 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
@@ -2108,7 +2121,7 @@ class DrupalWebTestCase extends DrupalTestCase {
               // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
               $post[$key] = urlencode($key) . '=' . urlencode($value);
             }
-            $post = implode('&', $post) . $extra_post;
+            $post = implode('&', $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.
@@ -2120,7 +2133,7 @@ class DrupalWebTestCase extends DrupalTestCase {
           }
           $this->verbose('POST request to: ' . $path .
                          '<hr />Ending URL: ' . $this->getUrl() .
-                         '<hr />Fields: ' . highlight_string('<?php ' . var_export($post_array, TRUE), TRUE) .
+                         '<hr />Fields: ' . highlight_string('<?php ' . var_export($post, TRUE), TRUE) .
                          '<hr />' . $out);
           return $out;
         }
@@ -2156,8 +2169,7 @@ class DrupalWebTestCase extends DrupalTestCase {
    *   and the value is the button label. i.e.) array('op' => t('Refresh')).
    * @param $ajax_path
    *   (optional) Override the path set by the Ajax settings of the triggering
-   *   element. In the absence of both the triggering element's Ajax path and
-   *   $ajax_path 'system/ajax' will be used.
+   *   element.
    * @param $options
    *   (optional) Options to be forwarded to url().
    * @param $headers
@@ -2184,8 +2196,10 @@ class DrupalWebTestCase extends DrupalTestCase {
     if (isset($path)) {
       $this->drupalGet($path, $options);
     }
+
     $content = $this->content;
     $drupal_settings = $this->drupalSettings;
+    $current_url = $this->url;
 
     // Get the Ajax settings bound to the triggering element.
     if (!isset($ajax_settings)) {
@@ -2204,31 +2218,31 @@ class DrupalWebTestCase extends DrupalTestCase {
     }
 
     // Add extra information to the POST data as ajax.js does.
-    $extra_post = '';
+    $extra_post = array();
     if (isset($ajax_settings['submit'])) {
       foreach ($ajax_settings['submit'] as $key => $value) {
-        $extra_post .= '&' . urlencode($key) . '=' . urlencode($value);
+        $extra_post[$key] = $value;
       }
     }
-    foreach ($this->xpath('//*[@id]') as $element) {
+    foreach ($this->xpath('//*[@id]') as $key => $element) {
       $id = (string) $element['id'];
-      $extra_post .= '&' . urlencode('ajax_html_ids[]') . '=' . urlencode($id);
+      $extra_post["ajax_html_ids[$key]"] = $id;
     }
     if (isset($drupal_settings['ajaxPageState'])) {
-      $extra_post .= '&' . urlencode('ajax_page_state[theme]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme']);
-      $extra_post .= '&' . urlencode('ajax_page_state[theme_token]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme_token']);
+      $extra_post['ajax_page_state[theme]'] = $drupal_settings['ajaxPageState']['theme'];
+      $extra_post['ajax_page_state[theme_token]'] = $drupal_settings['ajaxPageState']['theme_token'];
       foreach ($drupal_settings['ajaxPageState']['css'] as $key => $value) {
-        $extra_post .= '&' . urlencode("ajax_page_state[css][$key]") . '=1';
+        $extra_post["ajax_page_state[css][$key]"] = '1';
       }
       foreach ($drupal_settings['ajaxPageState']['js'] as $key => $value) {
-        $extra_post .= '&' . urlencode("ajax_page_state[js][$key]") . '=1';
+        $extra_post["ajax_page_state[js][$key]"] = '1';
       }
     }
 
     // Unless a particular path is specified, use the one specified by the
-    // Ajax settings, or else 'system/ajax'.
+    // Ajax settings, or else the current system path.
     if (!isset($ajax_path)) {
-      $ajax_path = isset($ajax_settings['url']) ? $ajax_settings['url'] : 'system/ajax';
+      $ajax_path = isset($ajax_settings['url']) ? $ajax_settings['url'] : NULL;
     }
 
     // Submit the POST request.
@@ -2258,8 +2272,14 @@ class DrupalWebTestCase extends DrupalTestCase {
             $wrapperNode = NULL;
             // When a command doesn't specify a selector, use the
             // #ajax['wrapper'] which is always an HTML ID.
-            if (!isset($command['selector'])) {
-              $wrapperNode = $xpath->query('//*[@id="' . $ajax_settings['wrapper'] . '"]')->item(0);
+            if (isset($ajax_settings['wrapper'])) {
+              $wrapper_id = $ajax_settings['wrapper'];
+            }
+            elseif (strpos($command['selector'], '#') === 0) {
+              $wrapper_id = str_replace('#', '', $command['selector']);
+            }
+            if (isset($wrapper_id)) {
+              $wrapperNode = $xpath->query('//*[@id="' . $wrapper_id . '"]')->item(0);
             }
             // @todo Ajax commands can target any jQuery selector, but these are
             //   hard to fully emulate with XPath. For now, just handle 'head'
@@ -2267,12 +2287,15 @@ class DrupalWebTestCase extends DrupalTestCase {
             elseif (in_array($command['selector'], array('head', 'body'))) {
               $wrapperNode = $xpath->query('//' . $command['selector'])->item(0);
             }
+            else {
+              $this->fail(format_string('The Ajax selector %string is not supported by the testing framework.', array('%string' => $command['selector'])));
+            }
             if ($wrapperNode) {
               // ajax.js adds an enclosing DIV to work around a Safari bug.
               $newDom = new DOMDocument();
               // DOM can load HTML soup. But, HTML soup can throw warnings,
               // suppress them.
-              $newDom->loadHTML('<div>' . $command['data'] . '</div>');
+              @$newDom->loadHTML('<div>' . $command['data'] . '</div>');
               // Suppress warnings thrown when duplicate HTML IDs are
               // encountered. This probably means we are replacing an element
               // with the same ID.
@@ -2335,7 +2358,7 @@ class DrupalWebTestCase extends DrupalTestCase {
       }
       $content = $dom->saveHTML();
     }
-    $this->drupalSetContent($content);
+    $this->drupalSetContent($content, $current_url);
     $this->drupalSetSettings($drupal_settings);
 
     $verbose = 'AJAX POST request to: ' . $path;
diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test
index afe0230..0300cbe 100644
--- a/modules/simpletest/tests/ajax.test
+++ b/modules/simpletest/tests/ajax.test
@@ -496,7 +496,7 @@ class AJAXMultiFormTestCase extends AJAXTestCase {
     // page update, ensure the same as above.
     foreach ($field_xpaths as $form_html_id => $field_xpath) {
       for ($i = 0; $i < 2; $i++) {
-        $this->drupalPostAJAX(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_html_id);
+        $this->drupalPostAJAX(NULL, array(), array($button_name => $button_value), NULL, array(), array(), $form_html_id);
         $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == $i+2, t('Found the correct number of field items after an AJAX submission.'));
         $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, t('Found the "add more" button after an AJAX submission.'));
         $this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other');
diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test
index 6bf2d9e..8a583cf 100644
--- a/modules/simpletest/tests/form.test
+++ b/modules/simpletest/tests/form.test
@@ -628,6 +628,8 @@ class FormAlterTestCase extends DrupalWebTestCase {
  * Test form validation handlers.
  */
 class FormValidationTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
       'name' => 'Form validation handlers',
@@ -748,6 +750,7 @@ class FormValidationTestCase extends DrupalWebTestCase {
  * Test form element labels, required markers and associated output.
  */
 class FormsElementsLabelsTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -835,6 +838,7 @@ class FormsElementsLabelsTestCase extends DrupalWebTestCase {
  * Test the tableselect form element for expected behavior.
  */
 class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1060,6 +1064,7 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
  * Test the vertical_tabs form element for expected behavior.
  */
 class FormsElementsVerticalTabsFunctionalTest extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1096,6 +1101,7 @@ class FormsElementsVerticalTabsFunctionalTest extends DrupalWebTestCase {
  * values aren't lost due to a wrong form rebuild.
  */
 class FormsFormStorageTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1106,7 +1112,7 @@ class FormsFormStorageTestCase extends DrupalWebTestCase {
   }
 
   function setUp() {
-    parent::setUp('form_test');
+    parent::setUp('form_test', 'dblog');
 
     $this->web_user = $this->drupalCreateUser(array('access content'));
     $this->drupalLogin($this->web_user);
@@ -1416,6 +1422,8 @@ class FormsFormStoragePageCacheTestCase extends DrupalWebTestCase {
  * Test wrapper form callbacks.
  */
 class FormsFormWrapperTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
       'name' => 'Form wrapper callback',
@@ -1442,6 +1450,8 @@ class FormsFormWrapperTestCase extends DrupalWebTestCase {
  * Test $form_state clearance.
  */
 class FormStateValuesCleanTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
       'name' => 'Form state values clearance',
@@ -1490,6 +1500,8 @@ class FormStateValuesCleanTestCase extends DrupalWebTestCase {
  * Tests $form_state clearance with form elements having buttons.
  */
 class FormStateValuesCleanAdvancedTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   /**
    * An image file path for uploading.
    */
@@ -1504,7 +1516,7 @@ class FormStateValuesCleanAdvancedTestCase extends DrupalWebTestCase {
   }
 
   function setUp() {
-    parent::setUp('form_test');
+    parent::setUp('form_test', 'file');
   }
 
   /**
@@ -1537,6 +1549,7 @@ class FormStateValuesCleanAdvancedTestCase extends DrupalWebTestCase {
  * @todo Add tests for other aspects of form rebuilding.
  */
 class FormsRebuildTestCase extends DrupalWebTestCase {
+
   public static function getInfo() {
     return array(
       'name' => 'Form rebuilding',
@@ -1604,7 +1617,7 @@ class FormsRebuildTestCase extends DrupalWebTestCase {
     // submission and verify it worked by ensuring the updated page has two text
     // field items in the field for which we just added an item.
     $this->drupalGet('node/add/page');
-    $this->drupalPostAJAX(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), 'system/ajax', array(), array(), 'page-node-form');
+    $this->drupalPostAJAX(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), NULL, array(), array(), 'page-node-form');
     $this->assert(count($this->xpath('//div[contains(@class, "field-name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
 
     // Submit the form with the non-Ajax "Save" button, leaving the title field
@@ -1629,6 +1642,7 @@ class FormsRebuildTestCase extends DrupalWebTestCase {
  * Tests form redirection.
  */
 class FormsRedirectTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1704,6 +1718,7 @@ class FormsRedirectTestCase extends DrupalWebTestCase {
  * Test the programmatic form submission behavior.
  */
 class FormsProgrammaticTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1814,6 +1829,7 @@ class FormsProgrammaticTestCase extends DrupalWebTestCase {
  * Test that FAPI correctly determines $form_state['triggering_element'].
  */
 class FormsTriggeringElementTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1912,6 +1928,8 @@ class FormsTriggeringElementTestCase extends DrupalWebTestCase {
  * Tests rebuilding of arbitrary forms by altering them.
  */
 class FormsArbitraryRebuildTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
       'name' => 'Rebuild arbitrary forms',
@@ -1978,6 +1996,7 @@ class FormsArbitraryRebuildTestCase extends DrupalWebTestCase {
  * Tests form API file inclusion.
  */
 class FormsFileInclusionTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
@@ -1995,7 +2014,7 @@ class FormsFileInclusionTestCase extends DrupalWebTestCase {
    * Tests loading an include specified in hook_menu().
    */
   function testLoadMenuInclude() {
-    $this->drupalPostAJAX('form-test/load-include-menu', array(), array('op' => t('Save')), 'system/ajax', array(), array(), 'form-test-load-include-menu');
+    $this->drupalPostAJAX('form-test/load-include-menu', array(), array('op' => t('Save')), NULL, array(), array(), 'form-test-load-include-menu');
     $this->assertText('Submit callback called.');
   }
 
@@ -2093,6 +2112,7 @@ class FormCheckboxTestCase extends DrupalWebTestCase {
  * Tests uniqueness of generated HTML IDs.
  */
 class HTMLIdTestCase extends DrupalWebTestCase {
+  protected $profile = 'testing';
 
   public static function getInfo() {
     return array(
diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module
index 4fd708f..8dcecf1 100644
--- a/modules/simpletest/tests/form_test.module
+++ b/modules/simpletest/tests/form_test.module
@@ -1877,6 +1877,15 @@ function form_test_two_instances() {
 }
 
 /**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function form_test_form_page_node_form_alter(&$form, &$form_state) {
+  // @todo: AJAXMultiFormTestCase depends on $form_state['cache'] being true.
+  // See https://www.drupal.org/node/2821852.
+  $form_state['cache'] = TRUE;
+}
+
+/**
  * Menu callback for testing custom form includes.
  */
 function form_test_load_include_custom($form, &$form_state) {
