diff --git a/core/drupalci.yml b/core/drupalci.yml
index ed02d43c63..14b4819e98 100644
--- a/core/drupalci.yml
+++ b/core/drupalci.yml
@@ -3,57 +3,11 @@
 # https://www.drupal.org/drupalorg/docs/drupal-ci/customizing-drupalci-testing
 build:
   assessment:
-    validate_codebase:
-      phplint:
-      csslint:
-        halt-on-fail: false
-      eslint:
-        # A test must pass eslinting standards check in order to continue processing.
-        halt-on-fail: false
-      phpcs:
-        # phpcs will use core's specified version of Coder.
-        sniff-all-files: false
-        halt-on-fail: false
     testing:
-      # run_tests task is executed several times in order of performance speeds.
-      # halt-on-fail can be set on the run_tests tasks in order to fail fast.
-      # suppress-deprecations is false in order to be alerted to usages of
-      # deprecated code.
-      run_tests.phpunit:
-        types: 'PHPUnit-Unit'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      run_tests.kernel:
-        types: 'PHPUnit-Kernel'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      run_tests.simpletest:
-         types: 'Simpletest'
-         testgroups: '--all'
-         suppress-deprecations: false
-         halt-on-fail: false
-      run_tests.build:
-        types: 'PHPUnit-Build'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
-      run_tests.functional:
-        types: 'PHPUnit-Functional'
-        testgroups: '--all'
-        suppress-deprecations: false
-        halt-on-fail: false
       run_tests.javascript:
-        concurrency: 15
+        concurrency: 1
         types: 'PHPUnit-FunctionalJavascript'
-        testgroups: '--all'
+        testgroups: '--class "Drupal\Tests\media_library\FunctionalJavascript\UploadFail"'
         suppress-deprecations: false
         halt-on-fail: false
-      # Run nightwatch testing.
-      # @see https://www.drupal.org/project/drupal/issues/2869825
-      nightwatchjs:
-#      container_command.drupal_project_templates:
-#        commands:
-#          - "sudo -u www-data ${SOURCE_DIR}/core/tests/scripts/test_composer_project_templates.sh"
-#        halt-on-fail: true
+
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index 8e7843fcbb..06bf2e62a1 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -85,6 +85,7 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
         // does for #element_validate and #process, this fills the missing
         // functionality to allow File fields to be extended through FAPI.
         if (isset($element['#file_value_callbacks'])) {
+          \Drupal::logger('file')->notice('#file_value_callbacks: '. serialize($element['#file_value_callbacks']));
           foreach ($element['#file_value_callbacks'] as $callback) {
             $callback($element, $input, $form_state);
           }
@@ -123,23 +124,32 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
             $fids = [];
           }
         }
+        else {
+          \Drupal::logger('file')->notice('$input[\'fids\'] empty at line 95 in ManagedFile::valueCallback()');
+        }
       }
     }
+    else {
+      \Drupal::logger('file')->notice('Input is FALSE in ManagedFile::valueCallback()');
+    }
 
     // If there is no input or if the default value was requested above, use the
     // default value.
     if ($input === FALSE || $force_default) {
       if ($element['#extended']) {
+        \Drupal::logger('file')->notice('$element[\'#extended\']');
         $default_fids = isset($element['#default_value']['fids']) ? $element['#default_value']['fids'] : [];
         $return = isset($element['#default_value']) ? $element['#default_value'] : ['fids' => []];
       }
       else {
+        \Drupal::logger('file')->notice('$element[\'#extended\'] FALSE');
         $default_fids = isset($element['#default_value']) ? $element['#default_value'] : [];
         $return = ['fids' => []];
       }
 
       // Confirm that the file exists when used as a default value.
       if (!empty($default_fids)) {
+        \Drupal::logger('file')->notice('empty $default_fids');
         $fids = [];
         foreach ($default_fids as $fid) {
           if ($file = File::load($fid)) {
@@ -149,6 +159,11 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
       }
     }
 
+    if (empty($fids)) {
+      $fids = [];
+      \Drupal::logger('file')->notice('fids empty, line 164 in ManagedFile');
+    }
+
     $return['fids'] = $fids;
     return $return;
   }
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/UploadFail.php b/core/modules/media_library/tests/src/FunctionalJavascript/UploadFail.php
new file mode 100644
index 0000000000..12f1c0feda
--- /dev/null
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/UploadFail.php
@@ -0,0 +1,272 @@
+<?php
+
+namespace Drupal\Tests\media_library\FunctionalJavascript;
+
+use Behat\Mink\Exception\ElementNotFoundException;
+use Drupal\Core\File\Exception\FileNotExistsException;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\media\Entity\Media;
+use Drupal\Tests\TestFileCreationTrait;
+use PHPUnit\Framework\AssertionFailedError;
+
+/**
+ * Checks if uploads fail after several attempts.
+ *
+ * @group media_library
+ */
+class UploadFail extends WebDriverTestBase {
+
+  use TestFileCreationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'media_library_test',
+    'node',
+    'dblog',
+  ];
+
+  /**
+   * Tests that repeated uploads to Media Library continue working.
+   */
+  public function testManyConsecutiveUploads() {
+    $page = $this->getSession()->getPage();
+
+    foreach ($this->getTestFiles('image') as $image) {
+      $extension = pathinfo($image->filename, PATHINFO_EXTENSION);
+      if ($extension === 'jpg') {
+        $jpg_image = $image;
+      }
+    }
+
+    if (!isset($jpg_image)) {
+      $this->fail('Expected test files not present.');
+    }
+
+    // Create a user that can only add media of type four.
+    $user = $this->drupalCreateUser([
+      'access administration pages',
+      'access content',
+      'create basic_page content',
+      'create type_one media',
+      'create type_four media',
+      'view media',
+    ]);
+    $this->drupalLogin($user);
+
+    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
+    $file_system = $this->container->get('file_system');
+    // Visit a node create page and open the media library.
+    $this->drupalGet('node/add/basic_page');
+
+    $database = $this->container->get('database');
+
+    $count = 0;
+    // Upload then remove an image 100x to see if/when it fails.
+    while ($count < 100) {
+      $this->assertElementExistsAfterWait('css', '.media-library-open-button[name^="field_twin_media"]', 10000, "Twin media open button not found on iteration $count")->click();
+      $this->assertElementExistsAfterWait('css', '.media-library-menu', 10000, "Media library menu did not appear on iteration $count");
+      $this->waitForText('Add or select media', 10000, "Text 'Add or select media' not appear on iteration $count");
+      $this->clickTypeTab('Four');
+      $new_filename = uniqid(time()) . '.jpg';
+      $new_filepath = $file_system->realpath('public://') . '/' . $new_filename;
+      $file_system->copy($file_system->realpath($jpg_image->uri), $new_filepath);
+      $this->waitForFieldExists('Add files', 10000, "Did not find 'Add files' on iteration $count");
+      $media_count = $database->select('file_managed')->countQuery()->execute()->fetchField();
+      $this->assertEquals($count, $media_count);
+      $page->attachFileToField('Add files', $new_filepath);
+      $result = $page->waitFor(10, function () use ($database, $count) {
+        $media_count = $database->select('file_managed')->countQuery()->execute()->fetchField();
+        return ($count + 1) === intval($media_count);
+      });
+      if (!$result) {
+        $logs = $database
+          ->select('watchdog', 'w')
+          ->fields('w', ['message', 'variables'])
+          ->condition('type', 'file')
+          ->orderBy('wid', 'DESC')
+          ->execute()
+          ->fetchAll();
+        $output = '';
+        foreach ($logs as $log) {
+          $output .= 'message: ' . $log->message . ', variables:  ' . $log->variables;
+        }
+
+        if (empty($output)) {
+          $output = "The file_managed table not updated on iteration $count.";
+        }
+        throw new AssertionFailedError($output);
+      }
+      $this->waitForText('Alternative text', 10000, "Alternative text field not found on iteration $count");
+      $field = $page->findField('Alternative text');
+      $this->assertTrue(!empty($field), "Alt text label found but not the field on iteration $count");
+      $field->setValue('alt text!');
+      $hidden_field_locator = '[name="media[0][fields][field_media_test_image][0][width]"]';
+      $hidden_field = $page->find('css', $hidden_field_locator);
+      $this->assertNotEmpty($hidden_field, "Width field not found on iteration $count");
+      $this->assertNotEmpty($hidden_field->getAttribute('value'), "Width field empty on iteration $count");
+      $this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane', 10000, "Save and select button not found on iteration $count")->pressButton('Save');
+      $this->waitForNoText('Save', 10000, "Save button visible when it shouldn't be on iteration $count");
+      $this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane', 10000, "Insert selected button not found on iteration $count")->pressButton('Insert selected');
+      $this->waitForText($new_filename, 10000, "$new_filename not found on iteration $count");
+      $this->waitForText('One media item remaining', 10000, "'One media item remaining' not found on iteration $count");
+      $this->assertElementExistsAfterWait('css', '[name="field_twin_media-0-media-library-remove-button"]', 10000, "Media remove button not found on iteration $count")->click();
+      $this->waitForText('2 media items remaining', 10000, "'2 media items remaining' not found on iteration $count");
+      $count++;
+      usleep(10000);
+    }
+
+  }
+
+  /**
+   * Asserts that text does not appear on page after a wait.
+   *
+   * @todo replace with whatever gets added in
+   *   https://www.drupal.org/node/3061852
+   */
+  protected function waitForNoText($text, $timeout = 10000, $message = 'text still present on page') {
+    $page = $this->getSession()->getPage();
+    $result = $page->waitFor($timeout / 1000, function () use ($page, $text) {
+      $actual = preg_replace('/\s+/u', ' ', $page->getText());
+      $regex = '/' . preg_quote($text, '/') . '/ui';
+      return (bool) !preg_match($regex, $actual);
+    });
+    if (empty($result)) {
+      $this->htmlOutput();
+      $filename = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '-waitNoText.jpg';
+      $this->createScreenshot($filename);
+    }
+    $this->assertNotEmpty($result, $message);
+  }
+
+  /**
+   * Asserts that text appears on page after a wait.
+   *
+   * @todo replace with whatever gets added in
+   *   https://www.drupal.org/node/3061852
+   */
+  protected function waitForText($text, $timeout = 10000, $message = 'text not found') {
+    $assert_session = $this->assertSession();
+    $result = $assert_session->waitForText($text, $timeout);
+    if (empty($result)) {
+      $this->htmlOutput();
+      $filename = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '-waitForText.jpg';
+      $this->createScreenshot($filename);
+    }
+    $this->assertNotEmpty($result, $message);
+  }
+
+  /**
+   * Waits for the specified selector and returns it if not empty.
+   *
+   * @param string $selector
+   *   The selector engine name. See ElementInterface::findAll() for the
+   *   supported selectors.
+   * @param string|array $locator
+   *   The selector locator.
+   * @param int $timeout
+   *   Timeout in milliseconds, defaults to 10000.
+   * @param string $message
+   *   Error message.
+   *
+   * @return \Behat\Mink\Element\NodeElement
+   *   The page element node if found. If not found, the test fails.
+   *
+   * @todo replace with whatever gets added in
+   *   https://www.drupal.org/node/3061852
+   */
+  protected function assertElementExistsAfterWait($selector, $locator, $timeout = 10000, $message = 'element not found after wait') {
+    $assert_session = $this->assertSession();
+    $element = $assert_session->waitForElement($selector, $locator, $timeout);
+    if (empty($element)) {
+      $this->htmlOutput();
+      $filename = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '-asertElExisAftWait.jpg';      $this->createScreenshot($filename);
+    }
+    $this->assertNotEmpty($element, $message);
+    return $element;
+  }
+
+  /**
+   * Clicks a media type tab and waits for it to appear.
+   */
+  protected function clickTypeTab($type) {
+    $page = $this->getSession()->getPage();
+    $assert_session = $this->assertSession();
+    $lowercase_type = strtolower($type);
+
+    try {
+      $assert_session->elementExists('css', ".media-library-menu-type-$lowercase_type .active-tab");
+      // There is nothing to do as the type is already active.
+      return;
+    }
+    catch (ElementNotFoundException $e) {
+    }
+
+    $page->clickLink($type);
+    $this->assertElementExistsAfterWait('css', ".media-library-menu-type-$lowercase_type .active-tab");
+    $assert_session->waitForElementVisible('css', "[action='/admin/content/media-widget/type_$lowercase_type']");
+  }
+
+  /**
+   * Checks for the existence of a field on page after wait.
+   *
+   * @param string $field
+   *   The field to find.
+   * @param int $timeout
+   *   (Optional) Timeout in milliseconds, defaults to 10000.
+   * @param string $message
+   *   Error message.
+   *
+   * @return \Behat\Mink\Element\NodeElement|null
+   *   The element if found, otherwise null.
+   *
+   * @todo replace with whatever gets added in
+   *   https://www.drupal.org/node/3061852
+   */
+  protected function waitForFieldExists($field, $timeout = 10000, $message = 'did not find field') {
+    $assert_session = $this->assertSession();
+    $page = $this->getSession()->getPage();
+
+    $start = microtime(TRUE);
+    $end = $start + ($timeout / 1000);
+    do {
+      $node = $page->findField($field);
+      if (!is_null($node)) {
+        return $node;
+      }
+      usleep(100000);
+    } while (microtime(TRUE) < $end);
+
+    $this->htmlOutput();
+    $filename = $this->htmlOutputDirectory . '/' . $this->htmlOutputClassName . '-' . $this->htmlOutputCounter . '-' . $this->htmlOutputTestId . '-waitFieExis.jpg';    $this->createScreenshot($filename);
+
+    $this->assertNotEmpty($node, $message);
+  }
+
+  /**
+   * Confirms the integrity of a copied file.
+   *
+   * @param string $uri
+   *   Uri of source file.
+   *
+   * @return string
+   *   The Uri of the copied file.
+   *
+   * @throws FileNotExistsException
+   *   If the copy does not match.
+   */
+  protected function copiedFileUri($uri) {
+    $file_system = $this->container->get('file_system');
+
+    $new_uri = $file_system->copy($uri, 'public://');
+    $original_hash = sha1_file($uri);
+    $new_hash = sha1_file($new_uri);
+    if ($original_hash === $new_hash) {
+      return $new_uri;
+    }
+    throw new FileNotExistsException("The copy of file $uri was not identical");
+  }
+
+}
