diff --git a/core/modules/file/src/Tests/FileFieldTestBase.php b/core/modules/file/src/Tests/FileFieldTestBase.php
index b7b90d7d37..91bb58ca7d 100644
--- a/core/modules/file/src/Tests/FileFieldTestBase.php
+++ b/core/modules/file/src/Tests/FileFieldTestBase.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\file\Tests;
 
+@trigger_error('The ' . __NAMESPACE__ . '\FileFieldTestBase is scheduled for removal in Drupal 9.0.0. Instead, use \Drupal\Tests\file\Functional\FileFieldTestBase', E_USER_DEPRECATED);
+
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\file\FileInterface;
diff --git a/core/modules/file/src/Tests/FileManagedTestBase.php b/core/modules/file/src/Tests/FileManagedTestBase.php
index 879e1c4e94..fde651dca6 100644
--- a/core/modules/file/src/Tests/FileManagedTestBase.php
+++ b/core/modules/file/src/Tests/FileManagedTestBase.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\file\Tests;
 
+@trigger_error('The ' . __NAMESPACE__ . '\FileManagedTestBase is scheduled for removal in Drupal 9.0.0. Instead, use \Drupal\Tests\file\Functional\FileManagedTestBase', E_USER_DEPRECATED);
+
 use Drupal\file\Entity\File;
 use Drupal\file\FileInterface;
 use Drupal\simpletest\WebTestBase;
diff --git a/core/modules/file/src/Tests/DownloadTest.php b/core/modules/file/tests/src/Functional/DownloadTest.php
similarity index 82%
rename from core/modules/file/src/Tests/DownloadTest.php
rename to core/modules/file/tests/src/Functional/DownloadTest.php
index 239155cf40..113af2071d 100644
--- a/core/modules/file/src/Tests/DownloadTest.php
+++ b/core/modules/file/tests/src/Functional/DownloadTest.php
@@ -1,6 +1,9 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
+
+use Drupal\Core\Site\Settings;
+use GuzzleHttp\Exception\RequestException;
 
 /**
  * Tests for download/file transfer functions.
@@ -25,16 +28,16 @@ public function testPublicFileTransfer() {
     // encoded.
     $filename = $GLOBALS['base_url'] . '/' . \Drupal::service('stream_wrapper_manager')->getViaScheme('public')->getDirectoryPath() . '/' . rawurlencode($file->getFilename());
     $this->assertEqual($filename, $url, 'Correctly generated a URL for a created file.');
-    $this->drupalHead($url);
-    $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the created file.');
-
+    $http_client = \Drupal::httpClient();
+    $response = $http_client->head($url);
+    $this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the created file.');
     // Test generating a URL to a shipped file (i.e. a file that is part of
     // Drupal core, a module or a theme, for example a JavaScript file).
     $filepath = 'core/assets/vendor/jquery/jquery.min.js';
     $url = file_create_url($filepath);
     $this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, 'Correctly generated a URL for a shipped file.');
-    $this->drupalHead($url);
-    $this->assertResponse(200, 'Confirmed that the generated URL is correct by downloading the shipped file.');
+    $response = $http_client->head($url);
+    $this->assertEquals(200, $response->getStatusCode(), 'Confirmed that the generated URL is correct by downloading the shipped file.');
   }
 
   /**
@@ -70,17 +73,28 @@ protected function doPrivateFileTransferTest() {
     $this->assertResponse(200, 'Correctly allowed access to a file when file_test provides headers.');
 
     // Test that the file transferred correctly.
-    $this->assertEqual($contents, $this->content, 'Contents of the file are correct.');
+    $this->assertSame($contents, $this->getSession()->getPage()->getContent(), 'Contents of the file are correct.');
 
     // Deny access to all downloads via a -1 header.
     file_test_set_return('download', -1);
-    $this->drupalHead($url);
-    $this->assertResponse(403, 'Correctly denied access to a file when file_test sets the header to -1.');
+    $http_client = \Drupal::httpClient();
+    try {
+      $http_client->head($url);
+      $this->fail('Not correctly denied access to a file when file_test sets the header to -1.');
+    }
+    catch (RequestException $e) {
+      $this->assertSame(403, $e->getCode());
+    }
 
     // Try non-existent file.
     $url = file_create_url('private://' . $this->randomMachineName());
-    $this->drupalHead($url);
-    $this->assertResponse(404, 'Correctly returned 404 response for a non-existent file.');
+    try {
+      $http_client->head($url);
+      $this->fail('Not correctly returned 404 response for a non-existent file.');
+    }
+    catch (RequestException $e) {
+      $this->assertSame(404, $e->getCode());
+    }
   }
 
   /**
@@ -172,4 +186,20 @@ private function checkUrl($scheme, $directory, $filename, $expected_url) {
     $file->delete();
   }
 
+  /**
+   * Changes in memory settings.
+   *
+   * @param $name
+   *   The name of the setting to return.
+   * @param $value
+   *   The value of the setting.
+   *
+   * @see \Drupal\Core\Site\Settings::get()
+   */
+  protected function settingsSet($name, $value) {
+    $settings = Settings::getAll();
+    $settings[$name] = $value;
+    new Settings($settings);
+  }
+
 }
diff --git a/core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php b/core/modules/file/tests/src/Functional/FileFieldAnonymousSubmissionTest.php
similarity index 99%
rename from core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php
rename to core/modules/file/tests/src/Functional/FileFieldAnonymousSubmissionTest.php
index 66d87cd7e8..9f004abba7 100644
--- a/core/modules/file/src/Tests/FileFieldAnonymousSubmissionTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldAnonymousSubmissionTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\node\Entity\Node;
 use Drupal\user\RoleInterface;
diff --git a/core/modules/file/src/Tests/FileFieldDisplayTest.php b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php
similarity index 98%
rename from core/modules/file/src/Tests/FileFieldDisplayTest.php
rename to core/modules/file/tests/src/Functional/FileFieldDisplayTest.php
index 9589db1785..01781b4904 100644
--- a/core/modules/file/src/Tests/FileFieldDisplayTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldDisplayTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\file\Entity\File;
@@ -102,7 +102,7 @@ public function testNodeDisplay() {
     // Uncheck the display checkboxes and go to the preview.
     $edit[$field_name . '[0][display]'] = FALSE;
     $edit[$field_name . '[1][display]'] = FALSE;
-    $this->drupalPostForm("node/$nid/edit", $edit, t('Preview'));
+    $this->drupalPostFormWithInvalidOptions("node/$nid/edit", $edit, t('Preview'));
     $this->clickLink(t('Back to content editing'));
     $this->assertRaw($field_name . '[0][display]', 'First file appears as expected.');
     $this->assertRaw($field_name . '[1][display]', 'Second file appears as expected.');
diff --git a/core/modules/file/src/Tests/FileFieldFormatterAccessTest.php b/core/modules/file/tests/src/Functional/FileFieldFormatterAccessTest.php
similarity index 95%
rename from core/modules/file/src/Tests/FileFieldFormatterAccessTest.php
rename to core/modules/file/tests/src/Functional/FileFieldFormatterAccessTest.php
index 68bb0473ef..5fbbdf14ff 100644
--- a/core/modules/file/src/Tests/FileFieldFormatterAccessTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldFormatterAccessTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 /**
  * Tests file formatter access.
diff --git a/core/modules/file/src/Tests/FileFieldPathTest.php b/core/modules/file/tests/src/Functional/FileFieldPathTest.php
similarity index 99%
rename from core/modules/file/src/Tests/FileFieldPathTest.php
rename to core/modules/file/tests/src/Functional/FileFieldPathTest.php
index 300b92c3ca..f4eabd457a 100644
--- a/core/modules/file/src/Tests/FileFieldPathTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldPathTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
 
diff --git a/core/modules/file/src/Tests/FileFieldRSSContentTest.php b/core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php
similarity index 89%
rename from core/modules/file/src/Tests/FileFieldRSSContentTest.php
rename to core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php
index af2b5f2b3e..26d14dbfb4 100644
--- a/core/modules/file/src/Tests/FileFieldRSSContentTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldRSSContentTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
 
@@ -60,12 +60,12 @@ public function testFileFieldRSSContent() {
     $this->drupalGet('rss.xml');
     $uploaded_filename = str_replace('public://', '', $node_file->getFileUri());
     $selector = sprintf(
-      'enclosure[url="%s"][length="%s"][type="%s"]',
+      'enclosure[@url="%s"][@length="%s"][@type="%s"]',
       file_create_url("public://$uploaded_filename", ['absolute' => TRUE]),
       $node_file->getSize(),
       $node_file->getMimeType()
     );
-    $this->assertTrue(!empty($this->cssSelect($selector)), 'File field RSS enclosure is displayed when viewing the RSS feed.');
+    $this->assertNotNull($this->getSession()->getDriver()->find('xpath', $selector), 'File field RSS enclosure is displayed when viewing the RSS feed.');
   }
 
 }
diff --git a/core/modules/file/src/Tests/FileFieldRevisionTest.php b/core/modules/file/tests/src/Functional/FileFieldRevisionTest.php
similarity index 99%
rename from core/modules/file/src/Tests/FileFieldRevisionTest.php
rename to core/modules/file/tests/src/Functional/FileFieldRevisionTest.php
index 04fbcb8767..6f05136cca 100644
--- a/core/modules/file/src/Tests/FileFieldRevisionTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldRevisionTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
 
diff --git a/core/modules/file/tests/src/Functional/FileFieldTestBase.php b/core/modules/file/tests/src/Functional/FileFieldTestBase.php
index d6096874d6..f7660ff24e 100644
--- a/core/modules/file/tests/src/Functional/FileFieldTestBase.php
+++ b/core/modules/file/tests/src/Functional/FileFieldTestBase.php
@@ -2,17 +2,18 @@
 
 namespace Drupal\Tests\file\Functional;
 
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\field\Entity\FieldConfig;
-use Drupal\file\FileInterface;
 use Drupal\Tests\BrowserTestBase;
-use Drupal\file\Entity\File;
+use Drupal\Tests\file\Traits\FileFieldTestBaseTrait;
+use Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait;
 
 /**
  * Provides methods specifically for testing File module's field handling.
  */
 abstract class FileFieldTestBase extends BrowserTestBase {
 
+  use DrupalPostFormWithInvalidOptionsTrait;
+  use FileFieldTestBaseTrait;
+
   /**
   * Modules to enable.
   *
@@ -34,287 +35,4 @@ protected function setUp() {
     $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
   }
 
-  /**
-   * Retrieves a sample file of the specified type.
-   *
-   * @return \Drupal\file\FileInterface
-   */
-  public function getTestFile($type_name, $size = NULL) {
-    // Get a file to upload.
-    $file = current($this->drupalGetTestFiles($type_name, $size));
-
-    // Add a filesize property to files as would be read by
-    // \Drupal\file\Entity\File::load().
-    $file->filesize = filesize($file->uri);
-
-    return File::create((array) $file);
-  }
-
-  /**
-   * Retrieves the fid of the last inserted file.
-   */
-  public function getLastFileId() {
-    return (int) db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
-  }
-
-  /**
-   * Creates a new file field.
-   *
-   * @param string $name
-   *   The name of the new field (all lowercase), exclude the "field_" prefix.
-   * @param string $entity_type
-   *   The entity type.
-   * @param string $bundle
-   *   The bundle that this field will be added to.
-   * @param array $storage_settings
-   *   A list of field storage settings that will be added to the defaults.
-   * @param array $field_settings
-   *   A list of instance settings that will be added to the instance defaults.
-   * @param array $widget_settings
-   *   A list of widget settings that will be added to the widget defaults.
-   */
-  public function createFileField($name, $entity_type, $bundle, $storage_settings = [], $field_settings = [], $widget_settings = []) {
-    $field_storage = FieldStorageConfig::create([
-      'entity_type' => $entity_type,
-      'field_name' => $name,
-      'type' => 'file',
-      'settings' => $storage_settings,
-      'cardinality' => !empty($storage_settings['cardinality']) ? $storage_settings['cardinality'] : 1,
-    ]);
-    $field_storage->save();
-
-    $this->attachFileField($name, $entity_type, $bundle, $field_settings, $widget_settings);
-    return $field_storage;
-  }
-
-  /**
-   * Attaches a file field to an entity.
-   *
-   * @param string $name
-   *   The name of the new field (all lowercase), exclude the "field_" prefix.
-   * @param string $entity_type
-   *   The entity type this field will be added to.
-   * @param string $bundle
-   *   The bundle this field will be added to.
-   * @param array $field_settings
-   *   A list of field settings that will be added to the defaults.
-   * @param array $widget_settings
-   *   A list of widget settings that will be added to the widget defaults.
-   */
-  public function attachFileField($name, $entity_type, $bundle, $field_settings = [], $widget_settings = []) {
-    $field = [
-      'field_name' => $name,
-      'label' => $name,
-      'entity_type' => $entity_type,
-      'bundle' => $bundle,
-      'required' => !empty($field_settings['required']),
-      'settings' => $field_settings,
-    ];
-    FieldConfig::create($field)->save();
-
-    entity_get_form_display($entity_type, $bundle, 'default')
-      ->setComponent($name, [
-        'type' => 'file_generic',
-        'settings' => $widget_settings,
-      ])
-      ->save();
-    // Assign display settings.
-    entity_get_display($entity_type, $bundle, 'default')
-      ->setComponent($name, [
-        'label' => 'hidden',
-        'type' => 'file_default',
-      ])
-      ->save();
-  }
-
-  /**
-   * Updates an existing file field with new settings.
-   */
-  public function updateFileField($name, $type_name, $field_settings = [], $widget_settings = []) {
-    $field = FieldConfig::loadByName('node', $type_name, $name);
-    $field->setSettings(array_merge($field->getSettings(), $field_settings));
-    $field->save();
-
-    entity_get_form_display('node', $type_name, 'default')
-      ->setComponent($name, [
-        'settings' => $widget_settings,
-      ])
-      ->save();
-  }
-
-  /**
-   * Uploads a file to a node.
-   *
-   * @param \Drupal\file\FileInterface $file
-   *   The File to be uploaded.
-   * @param string $field_name
-   *   The name of the field on which the files should be saved.
-   * @param $nid_or_type
-   *   A numeric node id to upload files to an existing node, or a string
-   *   indicating the desired bundle for a new node.
-   * @param bool $new_revision
-   *   The revision number.
-   * @param array $extras
-   *   Additional values when a new node is created.
-   *
-   * @return int
-   *   The node id.
-   */
-  public function uploadNodeFile(FileInterface $file, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) {
-    return $this->uploadNodeFiles([$file], $field_name, $nid_or_type, $new_revision, $extras);
-  }
-
-  /**
-   * Uploads multiple files to a node.
-   *
-   * @param \Drupal\file\FileInterface[] $files
-   *   The files to be uploaded.
-   * @param string $field_name
-   *   The name of the field on which the files should be saved.
-   * @param $nid_or_type
-   *   A numeric node id to upload files to an existing node, or a string
-   *   indicating the desired bundle for a new node.
-   * @param bool $new_revision
-   *   The revision number.
-   * @param array $extras
-   *   Additional values when a new node is created.
-   *
-   * @return int
-   *   The node id.
-   */
-  public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) {
-    $edit = [
-      'title[0][value]' => $this->randomMachineName(),
-      'revision' => (string) (int) $new_revision,
-    ];
-
-    $node_storage = $this->container->get('entity.manager')->getStorage('node');
-    if (is_numeric($nid_or_type)) {
-      $nid = $nid_or_type;
-      $node_storage->resetCache([$nid]);
-      $node = $node_storage->load($nid);
-    }
-    else {
-      // Add a new node.
-      $extras['type'] = $nid_or_type;
-      $node = $this->drupalCreateNode($extras);
-      $nid = $node->id();
-      // Save at least one revision to better simulate a real site.
-      $node->setNewRevision();
-      $node->save();
-      $node_storage->resetCache([$nid]);
-      $node = $node_storage->load($nid);
-      $this->assertNotEqual($nid, $node->getRevisionId(), 'Node revision exists.');
-    }
-
-    // Attach files to the node.
-    $field_storage = FieldStorageConfig::loadByName('node', $field_name);
-    // File input name depends on number of files already uploaded.
-    $field_num = count($node->{$field_name});
-    $name = 'files[' . $field_name . "_$field_num]";
-    if ($field_storage->getCardinality() != 1) {
-      $name .= '[]';
-    }
-    foreach ($files as $file) {
-      $file_path = $this->container->get('file_system')->realpath($file->getFileUri());
-      if (count($files) == 1) {
-        $edit[$name] = $file_path;
-      }
-      else {
-        $edit[$name][] = $file_path;
-      }
-    }
-    $this->drupalPostForm("node/$nid/edit", $edit, t('Save and keep published'));
-
-    return $nid;
-  }
-
-  /**
-   * Removes a file from a node.
-   *
-   * Note that if replacing a file, it must first be removed then added again.
-   */
-  public function removeNodeFile($nid, $new_revision = TRUE) {
-    $edit = [
-      'revision' => (string) (int) $new_revision,
-    ];
-
-    $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
-  }
-
-  /**
-   * Replaces a file within a node.
-   */
-  public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
-    $edit = [
-      'files[' . $field_name . '_0]' => drupal_realpath($file->getFileUri()),
-      'revision' => (string) (int) $new_revision,
-    ];
-
-    $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
-  }
-
-  /**
-   * Asserts that a file exists physically on disk.
-   *
-   * Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with
-   * file entities.
-   *
-   * @param \Drupal\File\FileInterface|string $file
-   *   Either the file entity or the file URI.
-   * @param string $message
-   *   (optional) A message to display with the assertion.
-   */
-  public static function assertFileExists($file, $message = NULL) {
-    $message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]);
-    $filename = $file instanceof FileInterface ? $file->getFileUri() : $file;
-    parent::assertFileExists($filename, $message);
-  }
-
-  /**
-   * Asserts that a file exists in the database.
-   */
-  public function assertFileEntryExists($file, $message = NULL) {
-    $this->container->get('entity.manager')->getStorage('file')->resetCache();
-    $db_file = File::load($file->id());
-    $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]);
-    $this->assertEqual($db_file->getFileUri(), $file->getFileUri(), $message);
-  }
-
-  /**
-   * Asserts that a file does not exist on disk.
-   *
-   * Overrides PHPUnit\Framework\Assert::assertFileExists() to also work with
-   * file entities.
-   *
-   * @param \Drupal\File\FileInterface|string $file
-   *   Either the file entity or the file URI.
-   * @param string $message
-   *   (optional) A message to display with the assertion.
-   */
-  public static function assertFileNotExists($file, $message = NULL) {
-    $message = isset($message) ? $message : format_string('File %file exists on the disk.', ['%file' => $file->getFileUri()]);
-    $filename = $file instanceof FileInterface ? $file->getFileUri() : $file;
-    parent::assertFileNotExists($filename, $message);
-  }
-
-  /**
-   * Asserts that a file does not exist in the database.
-   */
-  public function assertFileEntryNotExists($file, $message) {
-    $this->container->get('entity.manager')->getStorage('file')->resetCache();
-    $message = isset($message) ? $message : format_string('File %file exists in database at the correct path.', ['%file' => $file->getFileUri()]);
-    $this->assertFalse(File::load($file->id()), $message);
-  }
-
-  /**
-   * Asserts that a file's status is set to permanent in the database.
-   */
-  public function assertFileIsPermanent(FileInterface $file, $message = NULL) {
-    $message = isset($message) ? $message : format_string('File %file is permanent.', ['%file' => $file->getFileUri()]);
-    $this->assertTrue($file->isPermanent(), $message);
-  }
-
 }
diff --git a/core/modules/file/src/Tests/FileFieldValidateTest.php b/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
similarity index 99%
rename from core/modules/file/src/Tests/FileFieldValidateTest.php
rename to core/modules/file/tests/src/Functional/FileFieldValidateTest.php
index 4698185c4f..c307ce0fb2 100644
--- a/core/modules/file/src/Tests/FileFieldValidateTest.php
+++ b/core/modules/file/tests/src/Functional/FileFieldValidateTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\field\Entity\FieldConfig;
diff --git a/core/modules/file/src/Tests/FileListingTest.php b/core/modules/file/tests/src/Functional/FileListingTest.php
similarity index 98%
rename from core/modules/file/src/Tests/FileListingTest.php
rename to core/modules/file/tests/src/Functional/FileListingTest.php
index cca21c5a4c..0468f96666 100644
--- a/core/modules/file/src/Tests/FileListingTest.php
+++ b/core/modules/file/tests/src/Functional/FileListingTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\node\Entity\Node;
 use Drupal\file\Entity\File;
@@ -127,7 +127,7 @@ public function testFileListingPages() {
     $usage = $this->sumUsages($file_usage->listUsage($file));
     $this->assertRaw('admin/content/files/usage/' . $file->id() . '">' . $usage);
 
-    $result = $this->xpath("//td[contains(@class, 'views-field-status') and contains(text(), :value)]", [':value' => t('Temporary')]);
+    $result = $this->xpath("//td[contains(@class, 'views-field-status') and contains(text(), :value)]", [':value' => 'Temporary']);
     $this->assertEqual(1, count($result), 'Unused file marked as temporary.');
 
     // Test file usage page.
diff --git a/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php b/core/modules/file/tests/src/Functional/FileOnTranslatedEntityTest.php
similarity index 99%
rename from core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
rename to core/modules/file/tests/src/Functional/FileOnTranslatedEntityTest.php
index b8a22ae01b..508ead4742 100644
--- a/core/modules/file/src/Tests/FileOnTranslatedEntityTest.php
+++ b/core/modules/file/tests/src/Functional/FileOnTranslatedEntityTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
 
diff --git a/core/modules/file/src/Tests/FilePrivateTest.php b/core/modules/file/tests/src/Functional/FilePrivateTest.php
similarity index 95%
rename from core/modules/file/src/Tests/FilePrivateTest.php
rename to core/modules/file/tests/src/Functional/FilePrivateTest.php
index 0174a89dc8..6ab315780a 100644
--- a/core/modules/file/src/Tests/FilePrivateTest.php
+++ b/core/modules/file/tests/src/Functional/FilePrivateTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\Core\Entity\Plugin\Validation\Constraint\ReferenceAccessConstraint;
 use Drupal\Component\Utility\SafeMarkup;
@@ -80,7 +80,7 @@ public function testPrivateFile() {
     $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
     $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $edit[$field_name . '[0][fids]'] = $node_file->id();
-    $this->drupalPostForm('node/' . $new_node->id() . '/edit', $edit, t('Save'));
+    $this->drupalPostFormWithInvalidOptions('node/' . $new_node->id() . '/edit', $edit, t('Save'));
     // Make sure the form submit failed - we stayed on the edit form.
     $this->assertUrl('node/' . $new_node->id() . '/edit');
     // Check that we got the expected constraint form error.
@@ -91,7 +91,7 @@ public function testPrivateFile() {
     $edit = [];
     $edit['title[0][value]'] = $this->randomMachineName();
     $edit[$field_name . '[0][fids]'] = $node_file->id();
-    $this->drupalPostForm('node/add/' . $type_name, $edit, t('Save'));
+    $this->drupalPostFormWithInvalidOptions('node/add/' . $type_name, $edit, t('Save'));
     $new_node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
     $this->assertTrue(empty($new_node), 'Node was not created.');
     $this->assertUrl('node/add/' . $type_name);
@@ -142,9 +142,7 @@ public function testPrivateFile() {
     $this->drupalGet($file_url);
     $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the temporary file.');
     // Close the prior connection and remove the session cookie.
-    $this->curlClose();
-    $this->curlCookies = [];
-    $this->cookies = [];
+    $this->getSession()->reset();
     $this->drupalGet($file_url);
     $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the temporary file.');
 
@@ -172,9 +170,7 @@ public function testPrivateFile() {
     $this->drupalGet($file_url);
     $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the file whose references were removed.');
     // Close the prior connection and remove the session cookie.
-    $this->curlClose();
-    $this->curlCookies = [];
-    $this->cookies = [];
+    $this->getSession()->reset();
     $this->drupalGet($file_url);
     $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the file whose references were removed.');
 
@@ -195,9 +191,7 @@ public function testPrivateFile() {
     $this->drupalGet($file_url);
     $this->assertResponse(200, 'Confirmed that the anonymous uploader has access to the permanent file that is referenced by a published node.');
     // Close the prior connection and remove the session cookie.
-    $this->curlClose();
-    $this->curlCookies = [];
-    $this->cookies = [];
+    $this->getSession()->reset();
     $this->drupalGet($file_url);
     $this->assertResponse(200, 'Confirmed that another anonymous user also has access to the permanent file that is referenced by a published node.');
 
@@ -222,9 +216,7 @@ public function testPrivateFile() {
     $this->drupalGet($file_url);
     $this->assertResponse(403, 'Confirmed that the anonymous uploader cannot access the permanent file when it is referenced by an unpublished node.');
     // Close the prior connection and remove the session cookie.
-    $this->curlClose();
-    $this->curlCookies = [];
-    $this->cookies = [];
+    $this->getSession()->reset();
     $this->drupalGet($file_url);
     $this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
   }
diff --git a/core/modules/file/src/Tests/FileTokenReplaceTest.php b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
similarity index 99%
rename from core/modules/file/src/Tests/FileTokenReplaceTest.php
rename to core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
index 5bfa07b9ab..fd955913b9 100644
--- a/core/modules/file/src/Tests/FileTokenReplaceTest.php
+++ b/core/modules/file/tests/src/Functional/FileTokenReplaceTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\BubbleableMetadata;
diff --git a/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php b/core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php
similarity index 99%
rename from core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
rename to core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php
index da5abb5bd7..bc97a15e7c 100644
--- a/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
+++ b/core/modules/file/tests/src/Functional/PrivateFileOnTranslatedEntityTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
 use Drupal\node\Entity\Node;
diff --git a/core/modules/file/src/Tests/RemoteFileSaveUploadTest.php b/core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php
similarity index 90%
rename from core/modules/file/src/Tests/RemoteFileSaveUploadTest.php
rename to core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php
index d468ed326f..e0e4be4b3c 100644
--- a/core/modules/file/src/Tests/RemoteFileSaveUploadTest.php
+++ b/core/modules/file/tests/src/Functional/RemoteFileSaveUploadTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 /**
  * Tests the file uploading functions.
diff --git a/core/modules/file/src/Tests/SaveUploadFormTest.php b/core/modules/file/tests/src/Functional/SaveUploadFormTest.php
similarity index 98%
rename from core/modules/file/src/Tests/SaveUploadFormTest.php
rename to core/modules/file/tests/src/Functional/SaveUploadFormTest.php
index 6d51198a7f..1700834943 100644
--- a/core/modules/file/src/Tests/SaveUploadFormTest.php
+++ b/core/modules/file/tests/src/Functional/SaveUploadFormTest.php
@@ -1,8 +1,10 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
+use Drupal\Tests\TestFileCreationTrait;
+use Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait;
 
 /**
  * Tests the _file_save_upload_from_form() function.
@@ -13,6 +15,12 @@
  */
 class SaveUploadFormTest extends FileManagedTestBase {
 
+  use TestFileCreationTrait {
+    getTestFiles as drupalGetTestFiles;
+  }
+
+  use DrupalPostFormWithInvalidOptionsTrait;
+
   /**
    * Modules to enable.
    *
@@ -433,7 +441,7 @@ public function testCombinedErrorMessages() {
       'extensions' => 'jpeg',
     ];
 
-    $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
+    $this->drupalPostFormWithInvalidOptions('file-test/save_upload_from_form_test', $edit, t('Submit'));
     $this->assertResponse(200, 'Received a 200 response for posted test file.');
     $this->assertRaw(t('Epic upload FAIL!'), 'Found the failure message.');
 
diff --git a/core/modules/file/src/Tests/SaveUploadTest.php b/core/modules/file/tests/src/Functional/SaveUploadTest.php
similarity index 98%
rename from core/modules/file/src/Tests/SaveUploadTest.php
rename to core/modules/file/tests/src/Functional/SaveUploadTest.php
index a9f69c2fb7..9382c8b4a6 100644
--- a/core/modules/file/src/Tests/SaveUploadTest.php
+++ b/core/modules/file/tests/src/Functional/SaveUploadTest.php
@@ -1,8 +1,9 @@
 <?php
 
-namespace Drupal\file\Tests;
+namespace Drupal\Tests\file\Functional;
 
 use Drupal\file\Entity\File;
+use Drupal\Tests\TestFileCreationTrait;
 
 /**
  * Tests the file_save_upload() function.
@@ -10,6 +11,11 @@
  * @group file
  */
 class SaveUploadTest extends FileManagedTestBase {
+
+  use TestFileCreationTrait {
+    getTestFiles as drupalGetTestFiles;
+  }
+
   /**
    * Modules to enable.
    *
diff --git a/core/modules/file/src/Tests/Views/RelationshipUserFileDataTest.php b/core/modules/file/tests/src/Functional/Views/RelationshipUserFileDataTest.php
similarity index 92%
rename from core/modules/file/src/Tests/Views/RelationshipUserFileDataTest.php
rename to core/modules/file/tests/src/Functional/Views/RelationshipUserFileDataTest.php
index 6a5f53b5d3..0169e0a6d8 100644
--- a/core/modules/file/src/Tests/Views/RelationshipUserFileDataTest.php
+++ b/core/modules/file/tests/src/Functional/Views/RelationshipUserFileDataTest.php
@@ -1,10 +1,10 @@
 <?php
 
-namespace Drupal\file\Tests\Views;
+namespace Drupal\Tests\file\Functional\Views;
 
 use Drupal\field\Entity\FieldConfig;
 use Drupal\file\Entity\File;
-use Drupal\views\Tests\ViewTestBase;
+use Drupal\Tests\views\Functional\ViewTestBase;
 use Drupal\views\Views;
 use Drupal\views\Tests\ViewTestData;
 use Drupal\field\Entity\FieldStorageConfig;
@@ -30,8 +30,8 @@ class RelationshipUserFileDataTest extends ViewTestBase {
    */
   public static $testViews = ['test_file_user_file_data'];
 
-  protected function setUp() {
-    parent::setUp();
+  protected function setUp($import_test_views = TRUE) {
+    parent::setUp($import_test_views);
 
     // Create the user profile field and instance.
     FieldStorageConfig::create([
diff --git a/core/modules/file/tests/src/Functional/FileFieldTestBase.php b/core/modules/file/tests/src/Traits/FileFieldTestBaseTrait.php
similarity index 88%
copy from core/modules/file/tests/src/Functional/FileFieldTestBase.php
copy to core/modules/file/tests/src/Traits/FileFieldTestBaseTrait.php
index d6096874d6..9ce7058603 100644
--- a/core/modules/file/tests/src/Functional/FileFieldTestBase.php
+++ b/core/modules/file/tests/src/Traits/FileFieldTestBaseTrait.php
@@ -1,37 +1,51 @@
 <?php
 
-namespace Drupal\Tests\file\Functional;
+namespace Drupal\Tests\file\Traits;
 
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\file\FileInterface;
-use Drupal\Tests\BrowserTestBase;
 use Drupal\file\Entity\File;
+use Drupal\Tests\TestFileCreationTrait;
+use Drupal\user\UserInterface;
 
 /**
  * Provides methods specifically for testing File module's field handling.
  */
-abstract class FileFieldTestBase extends BrowserTestBase {
+trait FileFieldTestBaseTrait {
 
-  /**
-  * Modules to enable.
-  *
-  * @var array
-  */
-  public static $modules = ['node', 'file', 'file_module_test', 'field_ui'];
+  use TestFileCreationTrait {
+    getTestFiles as drupalGetTestFiles;
+  }
 
   /**
-   * An user with administration permissions.
+   * Creates a temporary file, for a specific user.
+   *
+   * @param string $data
+   *   A string containing the contents of the file.
+   * @param \Drupal\user\UserInterface $user
+   *   The user of the file owner.
    *
-   * @var \Drupal\user\UserInterface
+   * @return \Drupal\file\FileInterface
+   *   A file object, or FALSE on error.
    */
-  protected $adminUser;
+  protected function createTemporaryFile($data, UserInterface $user = NULL) {
+    $file = file_save_data($data, NULL, NULL);
+
+    if ($file) {
+      if ($user) {
+        $file->setOwner($user);
+      }
+      else {
+        $file->setOwner($this->adminUser);
+      }
+      // Change the file status to be temporary.
+      $file->setTemporary();
+      // Save the changes.
+      $file->save();
+    }
 
-  protected function setUp() {
-    parent::setUp();
-    $this->adminUser = $this->drupalCreateUser(['access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer node fields', 'administer node display', 'administer nodes', 'bypass node access']);
-    $this->drupalLogin($this->adminUser);
-    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+    return $file;
   }
 
   /**
@@ -182,7 +196,7 @@ public function uploadNodeFile(FileInterface $file, $field_name, $nid_or_type, $
    * @return int
    *   The node id.
    */
-  public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = []) {
+  public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_revision = TRUE, array $extras = [], $is_hack = FALSE) {
     $edit = [
       'title[0][value]' => $this->randomMachineName(),
       'revision' => (string) (int) $new_revision,
@@ -224,7 +238,12 @@ public function uploadNodeFiles(array $files, $field_name, $nid_or_type, $new_re
         $edit[$name][] = $file_path;
       }
     }
-    $this->drupalPostForm("node/$nid/edit", $edit, t('Save and keep published'));
+    if ($is_hack) {
+      $this->drupalPostFormWithInvalidOptions("node/$nid/edit", $edit, t('Save'));
+    }
+    else {
+      $this->drupalPostForm("node/$nid/edit", $edit, t('Save'));
+    }
 
     return $nid;
   }
@@ -240,7 +259,7 @@ public function removeNodeFile($nid, $new_revision = TRUE) {
     ];
 
     $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
   }
 
   /**
@@ -253,7 +272,7 @@ public function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE)
     ];
 
     $this->drupalPostForm('node/' . $nid . '/edit', [], t('Remove'));
-    $this->drupalPostForm(NULL, $edit, t('Save and keep published'));
+    $this->drupalPostForm(NULL, $edit, t('Save'));
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/DrupalPostFormWithInvalidOptionsTraitTest.php b/core/tests/Drupal/Tests/DrupalPostFormWithInvalidOptionsTraitTest.php
new file mode 100644
index 0000000000..9752ddac74
--- /dev/null
+++ b/core/tests/Drupal/Tests/DrupalPostFormWithInvalidOptionsTraitTest.php
@@ -0,0 +1,207 @@
+<?php
+
+namespace Drupal\Tests;
+
+use Behat\Mink\Exception\ElementNotFoundException;
+use Drupal\Tests\file\Traits\FileFieldTestBaseTrait;
+use Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait;
+use GuzzleHttp\Exception\ConnectException;
+
+/**
+ * @coversDefaultClass \Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait
+ * @group phpunit
+ */
+class DrupalPostFormWithInvalidOptionsTraitTest extends BrowserTestBase {
+
+  use DrupalPostFormWithInvalidOptionsTrait;
+  use FileFieldTestBaseTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['node', 'user', 'file', 'field_ui', 'file_test'];
+
+  /**
+   * A user with all permissions.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $admin;
+
+  /**
+   * A user with basic permissions.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $user;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create some users.
+    $this->admin = $this->drupalCreateUser([], 'admin user', TRUE);
+    $this->user = $this->drupalCreateUser(['access content']);
+
+    // Create Basic page and Article node types.
+    $this->drupalCreateContentType([
+      'type' => 'page',
+      'name' => 'Page',
+    ]);
+
+    // Create test node instance.
+    $this->drupalCreateNode([
+      'type' => 'page',
+    ]);
+
+    $this->drupalLogin($this->admin);
+  }
+
+  /**
+   * Tests post form request with valid values.
+   */
+  public function testParameters() {
+    // Check post request with set field value.
+    $edit = ['title[0][value]' => 'Test1'];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'));
+    $this->assertSession()->pageTextContains('Page Test1 has been updated');
+
+    // Check post request without edit values.
+    $this->drupalGet('node/1');
+    $this->assertSession()->pageTextNotContains('Page Test1 has been updated');
+    $this->drupalPostFormWithInvalidOptions('node/1/edit');
+    $this->assertSession()->pageTextContains('Page Test1 has been updated');
+
+    // Check post request without any arguments.
+    $this->drupalGet('node/1');
+    $this->assertSession()->pageTextNotContains('Page Test1 has been updated');
+    $this->drupalGet('node/1/edit');
+    $this->drupalPostFormWithInvalidOptions();
+    $this->assertSession()->pageTextContains('Page Test1 has been updated');
+
+    // Check post request with options.
+    $options = ['query' => ['destination' => 'node/add/page']];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', [], t('Save'), $options);
+    $this->assertSession()->pageTextContains('Page Test1 has been updated');
+    $this->assertSession()->pageTextContains('Create Page');
+
+    // Check post request with set header value.
+    try {
+      $headers = ['HTTP_HOST' => 'invalid-host'];
+      $this->drupalPostFormWithInvalidOptions('node/1/edit', [], t('Save'), [], $headers);
+      $this->assertSession()->pageTextContains('The requested URL was not found on this server.');
+    }
+    catch (ConnectException $e) {
+      $this->assertContains('cURL error 6: Could not resolve host: invalid-host', $e->getMessage());
+    }
+    // Check post request with with find form by ID.
+    $edit = ['title[0][value]' => 'Test by ID'];
+    $form_html_id = 'node-page-edit-form';
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, NULL, [], [], $form_html_id);
+    $this->assertSession()->pageTextContains('Page Test by ID has been updated');
+
+    // Check that raw arguments have more prior than $edit.
+    $edit = ['title[0][value]' => 'Test2'];
+    $raw_edit = ['form_params' => ['title' => [['value' => 'Test3']]]];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'), [], [], NULL, $raw_edit);
+    $this->assertSession()->pageTextContains('Page Test3 has been updated');
+  }
+
+  /**
+   * Tests post form request with non existent field.
+   */
+  public function testNonExistentField() {
+    // Check that edit params can contains values of nonexistent fields.
+    $edit = [
+      'title[0][value]' => 'Chell',
+      'cake' => 'is a lie',
+    ];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'));
+    $this->assertSession()->pageTextContains('Page Chell has been updated');
+
+    // Check that drupalPostForm() can not do this request.
+    try {
+      $this->drupalPostForm('node/1/edit', $edit, t('Save'));
+      $this->fail('drupalPostForm() does not work with nonexistent field');
+    }
+    catch (ElementNotFoundException $e) {
+      $this->assertSame('Form field with id|name|label|value "cake" not found.', $e->getMessage());
+    }
+  }
+
+  /**
+   * Tests post form request with hidden field.
+   */
+  public function testHiddenField() {
+    // Check post request with change hidden field.
+    $edit = ['changed' => 1234567890];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'));
+    $this->assertSession()->pageTextContains('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.');
+
+    // Check that drupalPostForm() can not do this request.
+    try {
+      $this->drupalPostForm('node/1/edit', $edit, t('Save'));
+      $this->fail('drupalPostForm() does not work with hidden field');
+    }
+    catch (ElementNotFoundException $e) {
+      $this->assertSame('Form field with id|name|label|value "changed" not found.', $e->getMessage());
+    }
+  }
+
+  /**
+   * Tests post form request with invalid field.
+   */
+  public function testInvalidFileField() {
+    $file = current($this->getTestFiles('text'));
+    $path = \Drupal::service('file_system')->realpath($file->uri);
+
+    // Check post request with invalid options.
+    $edit = [
+      'files[file_test_upload][]' => [
+        $path,
+        $path,
+      ],
+      'allow_all_extensions' => FALSE,
+      'is_image_file' => TRUE,
+      'extensions' => 'jpeg',
+    ];
+    $this->drupalPostFormWithInvalidOptions('file-test/save_upload_from_form_test', $edit, t('Submit'));
+    $this->assertSession()->pageTextContains('Epic upload FAIL!');
+
+    // Check that drupalPostForm() can not do this request.
+    try {
+      $this->drupalPostForm('file-test/save_upload_from_form_test', $edit, t('Submit'));
+      $this->fail('drupalPostForm() does not work with array instead of string');
+    }
+    catch (\PHPUnit_Framework_Error_Warning $e) {
+      $this->assertSame('is_readable() expects parameter 1 to be a valid path, array given', $e->getMessage());
+    }
+  }
+
+  /**
+   * Tests post form request with multi file field.
+   */
+  public function testMultiFileFieldWithLimit() {
+    $this->createFileField('field_file_multi', 'node', 'page', ['cardinality' => 2]);
+
+    $file = current($this->getTestFiles('text'));
+    $path = \Drupal::service('file_system')->realpath($file->uri);
+
+    // Check post request with more files, that limit of field.
+    $edit = ['files[field_file_multi_0][]' => [$path, $path, $path]];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'));
+    $this->assertSession()->pageTextContains('Field field_file_multi can only hold 2 values but there were 3 uploaded.');
+
+    // Check that drupalPostForm() can not do this request.
+    try {
+      $this->drupalPostForm('node/1/edit', $edit, t('Save'));
+      $this->fail('drupalPostForm() does not work with more files that cardinality of field');
+    }
+    catch (ElementNotFoundException $e) {
+      $this->assertSame('Form field with id|name|label|value "files[field_file_multi_0][]" not found.', $e->getMessage());
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php b/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php
new file mode 100644
index 0000000000..9c9e3a32ce
--- /dev/null
+++ b/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php
@@ -0,0 +1,186 @@
+<?php
+
+namespace Drupal\Tests\Traits;
+
+/**
+ * Provides API for send form with invalid options via POST request.
+ *
+ * This trait is meant to be used only by test classes of BrowserTestBase.
+ *
+ * The goal is to simplify as much as possible the collection of additional data
+ * required for post form. Such as session, values of other fields/properties of
+ * the form, the correct setting non-simple fields (e.g. multi files).
+ */
+trait DrupalPostFormWithInvalidOptionsTrait {
+
+  /**
+   * Executes POST request to form with unverified data.
+   *
+   * This method can be used when it is not appropriate to use drupalPostForm().
+   *
+   * @param \Drupal\Core\Url|string $path
+   *   (optional) Location of the post form.
+   *   Will be used current path if NULL value is set.
+   * @param array $edit
+   *   (optional) Field data in an associative array.
+   *   Use this parameter to describe the values of existing fields in the form.
+   * @param string|object $submit
+   *   (optional) Value of the submit button whose click is to be emulated.
+   *   Example: 'Save', t('Save').
+   * @param array $options
+   *   (optional) Options to be forwarded to the url generator.
+   * @param array $headers
+   *   (optional) An array containing additional HTTP request headers, each
+   *   formatted as "name: value".
+   * @param string $form_html_id
+   *   (optional) HTML ID of the form to be submitted.
+   * @param array $raw_edit
+   *   (optional) A array of additional data to append to the POST request.
+   *   Use this parameter for values that must be applied without processing.
+   *   Values will be taken from two keys 'form_multipart' and 'form_params':
+   *   Example:
+   *     [
+   *       'form_params' => ['title' => [['value' => 'Test']]],
+   *       'form_multipart' => [['files[field]' => 'path']],
+   *     ];
+   *   This is used when $edit values can not be correct divided. Like case when
+   *   it is need to edit values via multipart group, but the form does not have
+   *   file fields with such names, or need test several values for single file
+   *   fields.
+   */
+  protected function drupalPostFormWithInvalidOptions($path = NULL, array $edit = [], $submit = NULL, array $options = [], array $headers = [], $form_html_id = NULL, $raw_edit = []) {
+    if ($path !== NULL) {
+      $this->drupalGet($path, $options);
+    }
+
+    if (is_object($submit)) {
+      $submit = (string) $submit;
+    }
+
+    /** @var \Drupal\Tests\WebAssert $assert */
+    $assert = $this->assertSession();
+
+    // Get form and submit button.
+    if ($form_html_id !== NULL || $submit === NULL) {
+      $form_selector = '//form' . ($form_html_id ? "[@id='$form_html_id']" : '');
+      $form_element = $assert->elementExists('xpath', $form_selector);
+      $submit_button = ($submit !== NULL) ? $form_element->findField($submit) : $form_element->find('css', '[type="submit"]');
+    }
+    else {
+      $submit_button = $assert->buttonExists($submit);
+      $form_element = $assert->elementExists('xpath', './ancestor::form', $submit_button);
+    }
+
+    // Get form values.
+    $element = $submit_button ?: $form_element;
+    $crawler = $this->getSession()->getDriver()->getClient()->getCrawler();
+    $form = $crawler->filterXPath($element->getXpath())->form();
+    $form_params = $form->getPhpValues();
+    $form_multipart = $form->getPhpFiles();
+
+    // Get edit values.
+    $edit = $this->castSafeStrings($edit);
+    // Decouple multipart values (files) from other params.
+    $edit_params = [];
+    $edit_multipart = [];
+    foreach ($edit as $name => $value) {
+      $field = $form_element->findField($name);
+      if ($field && $field->getAttribute('type') === 'file') {
+        $edit_multipart[$name] = $value;
+      }
+      else {
+        $edit_params[$name] = $value;
+      }
+    }
+    $edit_multipart = $this->prepareFilesValue($edit_multipart);
+    $edit_params = $this->getPhpValues($edit_params);
+    $edit_multipart = $this->getPhpValues($edit_multipart);
+
+    // Get raw edit values.
+    $raw_edit_params = isset($raw_edit['form_params']) ? $raw_edit['form_params'] : [];
+    $raw_edit_multipart = isset($raw_edit['multipart']) ? $raw_edit['multipart'] : [];
+
+    // Combine form, edit and raw values.
+    $params = array_replace_recursive($form_params, $edit_params, $raw_edit_params);
+    $multipart = array_replace_recursive($form_multipart, $edit_multipart, $raw_edit_multipart);
+
+    $this->prepareRequest();
+
+    $client = $this->getSession()->getDriver()->getClient();
+    $method = $form_element->getAttribute('method') ?: 'POST';
+    $action = $form_element->getAttribute('action') ?: $this->getUrl();
+    $client->request($method, $action, $params, $multipart, $headers);
+
+    $this->refreshVariables();
+
+    // Check if there are any meta refresh redirects (like Batch API pages).
+    if ($this->checkForMetaRefresh()) {
+      // We are finished with all meta refresh redirects.
+      $this->metaRefreshCount = 0;
+    }
+  }
+
+  /**
+   * Expands the list of file values, and replaces paths on absolute path.
+   *
+   * Example:
+   *   [
+   *     'files[field_single]' => 'path0',
+   *     'files[field_multi][]' => ['path1', 'path2']
+   *   ]
+   * will be converted to
+   *   [
+   *     'files[field_single]' => 'real/path0',
+   *     'files[field_multi][0]' => 'real/path1',
+   *     'files[field_multi][1]' => 'real/path2',
+   *   ]
+   *
+   * @param array $files
+   *   List of files to prepare, each item as 'field_name' => value.
+   *
+   * @return array
+   *   Prepared array of files.
+   */
+  protected function prepareFilesValue($files) {
+    foreach ($files as $key => $paths) {
+      if (is_array($paths)) {
+        foreach ($paths as $i => $path) {
+          $new_key = str_replace('[]', "[$i]", $key);
+          $files[$new_key] = $path;
+        }
+        unset($files[$key]);
+      }
+    }
+    foreach ($files as $key => $path) {
+      $files[$key] = $this->container->get('file_system')->realpath($path);
+    }
+    return $files;
+  }
+
+  /**
+   * Gets the field values as PHP.
+   *
+   * Copied from \Symfony\Component\DomCrawler\Form::getPhpValues()
+   *
+   * This method converts fields with the array notation.
+   * Example:
+   *   ['foo[0][bar]' => 'value'] to ['foo' => [['bar' => 'value']].
+   *
+   * @return array
+   *   An array of field values.
+   */
+  public function getPhpValues($post) {
+    $values = array();
+    foreach ($post as $name => $value) {
+      $qs = http_build_query(array($name => $value), '', '&');
+      if (!empty($qs)) {
+        parse_str($qs, $expandedValue);
+        $varName = substr($name, 0, strlen(key($expandedValue)));
+        $values = array_replace_recursive($values, array($varName => current($expandedValue)));
+      }
+    }
+
+    return $values;
+  }
+
+}
