diff --git a/core/modules/file/src/Tests/FileFieldTestBase.php b/core/modules/file/src/Tests/FileFieldTestBase.php
index ba34c8f42b..12ab12390e 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 85%
rename from core/modules/file/src/Tests/DownloadTest.php
rename to core/modules/file/tests/src/Functional/DownloadTest.php
index 8f85f09f96..704c9db190 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,19 @@ 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.');
+
+    $http_client = $this->getSession()->getDriver()->getClient()->getClient();
 
     // 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.');
+    $response = $http_client->head($url, ['http_errors' => FALSE]);
+    $this->assertSame(403, $response->getStatusCode(), 'Correctly denied access to a file when file_test sets the header to -1.');
 
     // 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.');
+    $response = $http_client->head($url, ['http_errors' => FALSE]);
+    $this->assertSame(404, $response->getStatusCode(), 'Correctly returned 404 response for a non-existent file.');
   }
 
   /**
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 95%
rename from core/modules/file/src/Tests/FileFieldDisplayTest.php
rename to core/modules/file/tests/src/Functional/FileFieldDisplayTest.php
index 2b3786b0a5..6333a3e4c9 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;
@@ -97,15 +97,17 @@ public function testNodeDisplay() {
     // Test that fields appear as expected after during the preview.
     // Add a second file.
     $name = 'files[' . $field_name . '_1][]';
-    $edit[$name] = \Drupal::service('file_system')->realpath($test_file->getFileUri());
+    $edit_upload[$name] = \Drupal::service('file_system')->realpath($test_file->getFileUri());
+    $this->drupalPostForm('node/' . $nid . '/edit', $edit_upload, t('Upload'));
 
     // 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->drupalPostForm(NULL, $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.');
+    $this->assertSession()->responseContains($field_name . '[1][description]', 'Description of 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 f3d1c22b46..8561c108fd 100644
--- a/core/modules/file/tests/src/Functional/FileFieldTestBase.php
+++ b/core/modules/file/tests/src/Functional/FileFieldTestBase.php
@@ -7,6 +7,8 @@
 use Drupal\file\FileInterface;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\file\Entity\File;
+use Drupal\Tests\TestFileCreationTrait;
+use Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait;
 
 /**
  * Provides methods specifically for testing File module's field handling.
@@ -15,6 +17,11 @@
 
   use FileFieldCreationTrait;
 
+  use DrupalPostFormWithInvalidOptionsTrait;
+  use TestFileCreationTrait {
+    getTestFiles as drupalGetTestFiles;
+  }
+
   /**
   * Modules to enable.
   *
@@ -110,11 +117,14 @@ public function uploadNodeFile(FileInterface $file, $field_name, $nid_or_type, $
    *   The revision number.
    * @param array $extras
    *   Additional values when a new node is created.
+   * @param bool $is_invalid
+   *   FALSE (by default) using drupalPostForm()
+   *   TRUE using drupalPostFormWithInvalidOptions().
    *
    * @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_invalid = FALSE) {
     $edit = [
       'title[0][value]' => $this->randomMachineName(),
       'revision' => (string) (int) $new_revision,
@@ -156,7 +166,13 @@ 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_invalid) {
+      $this->drupalPostFormWithInvalidOptions("node/$nid/edit", $edit, t('Save'));
+    }
+    else {
+      $this->drupalPostForm("node/$nid/edit", $edit, t('Save'));
+    }
 
     return $nid;
   }
@@ -172,7 +188,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'));
   }
 
   /**
@@ -185,7 +201,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/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 be96f3c587..40c7771113 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 5c8b834b8b..b1d39b3ad8 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 e72f6d87eb..2e22a69c35 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 88574cb652..368e39165a 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;
@@ -82,7 +82,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.
@@ -93,7 +93,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);
@@ -144,9 +144,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.');
 
@@ -174,9 +172,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.');
 
@@ -197,9 +193,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.');
 
@@ -224,9 +218,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 2712ce09fd..7228e97a91 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 e568070616..14efd6a640 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.
    *
@@ -455,7 +463,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 a6e6f9a46a..40f560eec9 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 96%
rename from core/modules/file/src/Tests/Views/RelationshipUserFileDataTest.php
rename to core/modules/file/tests/src/Functional/Views/RelationshipUserFileDataTest.php
index 45e016d169..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;
diff --git a/core/tests/Drupal/FunctionalTests/DrupalPostFormWithInvalidOptionsTraitTest.php b/core/tests/Drupal/FunctionalTests/DrupalPostFormWithInvalidOptionsTraitTest.php
new file mode 100644
index 0000000000..a0f298cbf2
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/DrupalPostFormWithInvalidOptionsTraitTest.php
@@ -0,0 +1,267 @@
+<?php
+
+namespace Drupal\FunctionalTests;
+
+use Behat\Mink\Exception\ElementNotFoundException;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Tests\file\Functional\FileFieldTestBase;
+use Drupal\Tests\Traits\DrupalPostFormWithInvalidOptionsTrait;
+use GuzzleHttp\Exception\ConnectException;
+
+/**
+ * Test post form with invalid options.
+ *
+ * @group phpunit
+ */
+class DrupalPostFormWithInvalidOptionsTraitTest extends FileFieldTestBase {
+
+  use DrupalPostFormWithInvalidOptionsTrait;
+
+  /**
+   * {@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 priority than $edit.
+    $edit = ['title[0][value]' => 'Test2'];
+    $params = ['title' => [['value' => 'Test3']]];
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Save'), [], [], NULL, $params);
+    $this->assertSession()->pageTextContains('Page Test3 has been updated');
+  }
+
+  /**
+   * Tests post form requests with non-existent fields.
+   */
+  public function testNonExistentField() {
+    // Check that edit params can contain values of non-existent 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() cannot 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 requests with hidden fields.
+   */
+  public function testHiddenField() {
+    // Check post request with changed 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() cannot 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 requests with invalid fields.
+   */
+  public function testInvalidFileField() {
+    $file = $this->getTestFile('text');
+    $path = \Drupal::service('file_system')->realpath($file->getFileUri());
+
+    // 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() cannot 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 requests with multi file fields.
+   */
+  public function testMultiFileFieldWithLimit() {
+    $this->createFileField('field_file_multi', 'node', 'page', ['cardinality' => 2]);
+
+    $file = $this->getTestFile('text');
+    $path = \Drupal::service('file_system')->realpath($file->getFileUri());
+
+    // Check post request with more files than the limit set on the 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() cannot 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());
+    }
+  }
+
+  /**
+   * Tests post form requests when a field already contains a file.
+   */
+  public function testAddValuesForFieldWithExistValues() {
+    $field_storage_settings = [
+      'display_field' => '1',
+      'display_default' => '0',
+      'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
+    ];
+    $field_settings = [
+      'description_field' => '1',
+    ];
+    $this->createFileField('field_file_multi', 'node', 'page', $field_storage_settings, $field_settings);
+    // Add 1 file to field.
+    $test_file = $this->getTestFile('text');
+    $test_file_path = \Drupal::service('file_system')->realpath($test_file->getFileUri());
+    $this->uploadNodeFile($test_file, 'field_file_multi', 1);
+
+    $assert = $this->assertSession();
+
+    // Edit first value.
+    $edit['field_file_multi[0][description]'] = 'description0';
+    $edit['field_file_multi[0][display]'] = '1';
+    // Add second value.
+    $edit['files[field_file_multi_1][]'] = $test_file_path;
+    $edit['field_file_multi[1][description]'] = 'description1';
+    $edit['field_file_multi[1][display]'] = '1';
+
+    // Test 'Preview' request without pre-save values.
+    $this->drupalPostFormWithInvalidOptions('node/1/edit', $edit, t('Preview'));
+    $assert->linkExistsExact('description0');
+    $assert->linkExistsExact('description1');
+
+    $this->clickLink(t('Back to content editing'));
+
+    // Ensure that existing field values are changed.
+    $this->assertSame('description0', $assert->fieldExists('field_file_multi[0][description]')->getValue());
+    $this->assertSame('1', $assert->fieldExists('field_file_multi[0][display]')->getAttribute('value'));
+    // Ensure that non-existing field values are lost.
+    $assert->fieldNotExists('field_file_multi[1][description]');
+    $assert->fieldNotExists('field_file_multi[1][display]');
+
+    // Test 'Upload' request.
+    $this->drupalPostFormWithInvalidOptions(NULL, $edit, t('Upload'));
+    $this->assertSame('description0', $assert->fieldExists('field_file_multi[0][description]')->getValue());
+    $this->assertSame('description1', $assert->fieldExists('field_file_multi[1][description]')->getValue());
+
+    // Test 'Preview' request with pre-save values (after upload new file).
+    $edit['field_file_multi[0][description]'] = 'description0-1';
+    $edit['field_file_multi[1][description]'] = 'description1-1';
+    $this->drupalPostFormWithInvalidOptions(NULL, $edit, t('Preview'));
+    $assert->linkExistsExact('description0-1');
+    $assert->linkExistsExact('description1-1');
+    $this->clickLink(t('Back to content editing'));
+    // Ensure that both values are changed.
+    $this->assertSame('description0-1', $assert->fieldExists('field_file_multi[0][description]')->getValue());
+    $this->assertSame('description1-1', $assert->fieldExists('field_file_multi[1][description]')->getValue());
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php b/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php
new file mode 100644
index 0000000000..a4339154b3
--- /dev/null
+++ b/core/tests/Drupal/Tests/Traits/DrupalPostFormWithInvalidOptionsTrait.php
@@ -0,0 +1,228 @@
+<?php
+
+namespace Drupal\Tests\Traits;
+
+/**
+ * Provides API for sending a form with invalid options via POST request.
+ *
+ * This trait is meant to be used only by test classes of BrowserTestBase.
+ *
+ * Mink doesn't allow the setting of invalid data in a form, or the modification
+ * of hidden field. This trait can be used when these types of situations need
+ * to be tested.
+ *
+ * See more examples in \Drupal\Tests\DrupalPostFormWithInvalidOptionsTraitTest.
+ */
+trait DrupalPostFormWithInvalidOptionsTrait {
+
+  /**
+   * Executes POST request to form with invalid options.
+   *
+   * This method can be used when it is not appropriate to use drupalPostForm().
+   *
+   * This can be used when needing to test:
+   * - changing hidden fields.
+   * - adding values without fields.
+   * - setting invalid values by type data or limit.
+   * - sending a form with special headers.
+   *
+   * @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.
+   *   By default, the first button with submit type will be used.
+   *   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". It will be used as $server parameter in
+   *   \Symfony\Component\BrowserKit::request().
+   * @param string $form_html_id
+   *   (optional) HTML ID of the form to be submitted. It is necessary, when the
+   *   $submit parameter is not specified, or on the page there are several
+   *   corresponding forms.
+   * @param array $raw_params
+   * @param array $raw_files
+   *   (optional) Additional data to append to the request.
+   *   It will be transferred to \Symfony\Component\BrowserKit::request() as is.
+   *   Wherein:
+   *     'form_params' -> $parameters
+   *     'form_multipart' -> $files
+   *   Example:
+   *     [
+   *       'form_params' => ['title' => [['value' => 'Test']]],
+   *       'form_multipart' => [['files[field]' => 'path']],
+   *     ];
+   *   Use this to avoid processing, such as $edit parameter.
+   */
+  protected function drupalPostFormWithInvalidOptions($path = NULL, array $edit = [], $submit = NULL, array $options = [], array $headers = [], $form_html_id = NULL, $raw_params = [], $raw_files=[]) {
+    $form = $this->getCrawlerForm($path, $options, $submit, $form_html_id);
+    $form_params = $form->getPhpValues();
+    $form_files = $form->getPhpFiles();
+
+    $edit_params = $this->prepareEditValues($edit);
+    $edit_files = [];
+    if (isset($edit_params['files'])) {
+      $edit_files['files'] = $edit_params['files'];
+      unset($edit_params['files']);
+    }
+
+    $params = array_replace_recursive($form_params, $edit_params, $raw_params);
+    $files = array_replace_recursive($form_files, $edit_files, $raw_files);
+
+    $method = $form->getMethod() ?: 'POST';
+    $action = $form->getUri() ?: $this->getUrl();
+
+    $this->drupalRequest($method, $action, $params, $files, $headers);
+  }
+
+  /**
+   * Perform a request with update browser page.
+   *
+   * For send the form use:
+   *   drupalPostForm() with valid data
+   *   drupalPostFormWithInvalidOptions() with invalid data
+   * For navigation use drupalGet().
+   * For more flexible request use http client.
+   */
+  protected function drupalRequest($method, $action, $params = [], $multipart = [], $headers = []) {
+    $this->prepareRequest();
+    $client = $this->getSession()->getDriver()->getClient();
+    $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;
+    }
+  }
+
+  /**
+   * Gets the crawler form by html ID or button name on the page.
+   *
+   * @return \Symfony\Component\DomCrawler\Form
+   *   Crawler form.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   */
+  protected function getCrawlerForm($path = NULL, $options = NULL, $submit = NULL, $form_html_id = NULL) {
+    if ($path !== NULL) {
+      $this->drupalGet($path, $options);
+    }
+
+    if (is_object($submit)) {
+      $submit = (string) $submit;
+    }
+
+    /** @var \Drupal\Tests\WebAssert $assert */
+    $assert = $this->assertSession();
+    if ($form_html_id !== NULL) {
+      $form_element = $assert->elementExists('xpath', "//form[@id='$form_html_id']");
+      $submit_button = ($submit !== NULL) ? $form_element->findField($submit) : $form_element->find('css', '[type="submit"]');
+    }
+    elseif ($submit !== NULL) {
+      $submit_button = $assert->buttonExists($submit);
+      $form_element = $assert->elementExists('xpath', './ancestor::form', $submit_button);
+    }
+    else {
+      $form_element = $assert->elementExists('xpath', '//form');
+      $submit_button = $form_element->find('css', '[type="submit"]');
+    }
+
+    $element = $submit_button ?: $form_element;
+    $crawler = $this->getSession()->getDriver()->getClient()->getCrawler();
+    $form = $crawler->filterXPath($element->getXpath())->form();
+
+    return $form;
+  }
+
+  /**
+   * Prepare edit values to request.
+   *
+   * @return array
+   *   An array with prepared values.
+   */
+  protected function prepareEditValues(array $edit = []) {
+    $edit = $this->castSafeStrings($edit);
+    $edit = $this->convertMultipleValues($edit);
+    $edit = $this->getPhpValues($edit);
+    return $edit;
+  }
+
+  /**
+   * Convert item with array values to items with string values.
+   *
+   * Example:
+   *   [
+   *     'key1' => 'value1',
+   *     'key2[]' => ['value2', 'value3']
+   *   ]
+   * will be converted to
+   *   [
+   *     'key1' => 'value1',
+   *     'key2[0]' => 'value2',
+   *     'key2[1]' => 'value3',
+   *   ]
+   *
+   * @param array $values
+   *   List of files to prepare, each item as 'field_name' => value.
+   *
+   * @return array
+   *   Prepared array of values after converting.
+   */
+  protected function convertMultipleValues(array $values) {
+    foreach ($values as $key => $item) {
+      if (is_array($item)) {
+        foreach ($item as $i => $value) {
+          $new_key = str_replace('[]', "[$i]", $key);
+          $values[$new_key] = $value;
+        }
+        unset($values[$key]);
+      }
+    }
+    return $values;
+  }
+
+  /**
+   * 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',
+   *   ]
+   * will be converted to
+   *   [
+   *     'foo' => [
+   *       [
+   *         'bar' => 'value',
+   *       ],
+   *     ],
+   *   ]
+   *
+   * @param array $post
+   *   An array of field values with the string notation.
+   *
+   * @return array
+   *   An array of field values with the array notation.
+   */
+  protected function getPhpValues($post) {
+    $values = [];
+    foreach ($post as $name => $value) {
+      $qs = http_build_query([$name => $value], '', '&');
+      if (!empty($qs)) {
+        parse_str($qs, $expandedValue);
+        $values = array_replace_recursive($values, $expandedValue);
+      }
+    }
+    return $values;
+  }
+
+}
