From da8a3360afc3d62a52bf99061d986cde264dab69 Mon Sep 17 00:00:00 2001
From: Lauri Eskola <lauri.eskola@acquia.com>
Date: Fri, 15 Dec 2023 13:48:47 +0200
Subject: [PATCH] Issue #3408326 by benjifisher, omkar.podey, lauriii,
 srishtiiee: Make field selection a two-step form

---
 .../src/Functional/CommentFieldsTest.php      |   5 +-
 .../EntityReferenceAdminTest.php              |   4 +-
 .../field_ui/src/Form/FieldStorageAddForm.php | 353 ++++++++++--------
 .../Functional/ManageFieldsFunctionalTest.php |  35 +-
 .../tests/src/Functional/ManageFieldsTest.php |  25 +-
 .../FunctionalJavascript/ManageFieldsTest.php |  38 +-
 .../tests/src/Traits/FieldUiJSTestTrait.php   |   4 +-
 .../tests/src/Traits/FieldUiTestTrait.php     |  31 +-
 core/modules/media/media.module               |   2 +-
 .../MediaReferenceFieldHelpTest.php           |   6 +-
 .../FieldUiIntegrationTest.php                |   1 +
 .../Functional/NodeTypeTranslationTest.php    |   2 +
 12 files changed, 305 insertions(+), 201 deletions(-)

diff --git a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
index ad42c61a6553..0ff66475f961 100644
--- a/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
@@ -154,10 +154,13 @@ public function testCommentFieldCreate() {
     // Create comment field in account settings.
     $edit = [
       'new_storage_type' => 'comment',
+    ];
+    $this->drupalGet('admin/config/people/accounts/fields/add-field');
+    $this->submitForm($edit, 'Continue');
+    $edit = [
       'label' => 'User comment',
       'field_name' => 'user_comment',
     ];
-    $this->drupalGet('admin/config/people/accounts/fields/add-field');
     $this->submitForm($edit, 'Continue');
 
     // Try to save the comment field without selecting a comment type.
diff --git a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php
index e7ed6abe1f9a..9249c4ebfd61 100644
--- a/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php
+++ b/core/modules/field/tests/src/FunctionalJavascript/EntityReference/EntityReferenceAdminTest.php
@@ -125,10 +125,12 @@ public function testFieldAdminHandler() {
 
     // Check if the commonly referenced entity types appear in the list.
     $page->find('css', "[name='new_storage_type'][value='reference']")->getParent()->click();
-    $assert_session->waitForText('Choose an option below');
+    $page->pressButton('Continue');
+    $assert_session->pageTextContains('Choose an option below');
     $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='field_ui:entity_reference:node']");
     $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='field_ui:entity_reference:user']");
 
+    $page->pressButton('Back');
     $this->fieldUIAddNewFieldJS(NULL, 'test', 'Test', 'entity_reference', FALSE);
 
     // Node should be selected by default.
diff --git a/core/modules/field_ui/src/Form/FieldStorageAddForm.php b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
index 6d5ff8bc3145..242ea7b9a8a8 100644
--- a/core/modules/field_ui/src/Form/FieldStorageAddForm.php
+++ b/core/modules/field_ui/src/Form/FieldStorageAddForm.php
@@ -131,57 +131,167 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
     $this->entityTypeId = $form_state->get('entity_type_id');
     $this->bundle = $form_state->get('bundle');
 
+    if (!$form_state->has('field_type_options') || !$form_state->has('unique_definitions')) {
+      $this->processFieldDefinitions($form_state);
+    }
+
+    // Place the 'translatable' property as an explicit value so that contrib
+    // modules can form_alter() the value for newly created fields. By default
+    // we create field storage as translatable so it will be possible to enable
+    // translation at field level.
+    $form['translatable'] = [
+      '#type' => 'value',
+      '#value' => TRUE,
+    ];
+
+    $form['actions'] = ['#type' => 'actions'];
+    $form['actions']['submit'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Continue'),
+      '#button_type' => 'primary',
+    ];
+
+    $form['#attached']['library'] = [
+      'field_ui/drupal.field_ui',
+      'field_ui/drupal.field_ui.manage_fields',
+      'core/drupal.ajax',
+    ];
+    // The group info is stored in new_storage_type.
+    if ($form_state->getValue('new_storage_type')) {
+      // A group is already selected. Show field types for that group.
+      $this->addFieldOptionsForGroup($form, $form_state);
+    }
+    else {
+      // Show options for groups and ungrouped field types.
+      $this->addGroupFieldOptions($form, $form_state);
+    }
+
+    return $form;
+  }
+
+  /**
+   * Adds field types for the selected group to the form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function addFieldOptionsForGroup(array &$form, FormStateInterface $form_state): void {
     // Field label and field_name.
     $form['new_storage_wrapper'] = [
       '#type' => 'container',
       '#attributes' => [
         'class' => ['field-ui-new-storage-wrapper'],
       ],
-      '#states' => [
-        '!visible' => [
-          ':input[name="new_storage_type"]' => ['value' => ''],
-        ],
-      ],
     ];
     $form['new_storage_wrapper']['label'] = [
       '#type' => 'textfield',
       '#title' => $this->t('Label'),
       '#size' => 30,
     ];
+    $field_prefix = $this->config('field_ui.settings')->get('field_prefix');
+    $form['new_storage_wrapper']['field_name'] = [
+      '#type' => 'machine_name',
+      '#field_prefix' => $field_prefix,
+      '#size' => 15,
+      '#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
+      // Calculate characters depending on the length of the field prefix
+      // setting. Maximum length is 32.
+      '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
+      '#machine_name' => [
+        'source' => ['new_storage_wrapper', 'label'],
+        'exists' => [$this, 'fieldNameExists'],
+      ],
+      '#required' => FALSE,
+    ];
 
-    $field_type_options = $unique_definitions = [];
-    $grouped_definitions = $this->fieldTypePluginManager->getGroupedDefinitions($this->fieldTypePluginManager->getEntityTypeUiDefinitions($this->entityTypeId), 'label', 'id');
-    $category_definitions = $this->fieldTypeCategoryManager->getDefinitions();
-    // Invoke a hook to get category properties.
-    foreach ($grouped_definitions as $category => $field_types) {
-      foreach ($field_types as $name => $field_type) {
-        $unique_definitions[$category][$name] = ['unique_identifier' => $name] + $field_type;
-        if ($this->fieldTypeCategoryManager->hasDefinition($category)) {
-          $category_plugin = $this->fieldTypeCategoryManager->createInstance($category, $unique_definitions[$category][$name], $category_definitions[$category]);
-          $field_type_options[$category_plugin->getPluginId()] = ['unique_identifier' => $name] + $field_type;
-        }
-        else {
-          $field_type_options[(string) $field_type['label']] = ['unique_identifier' => $name] + $field_type;
-        }
-      }
+    $form['actions']['submit']['#validate'][] = '::validateAddNew';
+
+    $form['actions']['back'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Back'),
+      '#submit' => ['::startOver'],
+    ];
+
+    $field_type_options = $form_state->get('field_type_options');
+    $new_storage_type = $form_state->getValue('new_storage_type');
+    $form['new_storage_type'] = [
+      '#type' => 'value',
+      '#value' => $new_storage_type,
+    ];
+    if (!isset($new_storage_type) || !$field_type_options[$new_storage_type]['display_as_group']) {
+      return;
     }
-    $form['add-label'] = [
+    // Create a wrapper for all the field options to be provided.
+    $form['group_field_options_wrapper'] = [
+      '#prefix' => '<div id="group-field-options-wrapper" class="group-field-options-wrapper">',
+      '#suffix' => '</div>',
+    ];
+    $form['group_field_options_wrapper']['label'] = [
       '#type' => 'label',
-      '#title' => t('Choose a type of field'),
+      '#title' => $this->t('Choose an option below'),
       '#required' => TRUE,
     ];
-
-    $form['add'] = [
+    $form['group_field_options_wrapper']['fields'] = [
       '#type' => 'container',
       '#attributes' => [
-        'class' => 'add-field-container',
+        'class' => ['group-field-options'],
       ],
     ];
+
+    $unique_definitions = $form_state->get('unique_definitions');
+    $group_field_options = [];
+    foreach ($unique_definitions[$new_storage_type] as $option_key => $option) {
+      $radio_element = [
+        '#type' => 'radio',
+        '#theme_wrappers' => ['form_element__new_storage_type'],
+        '#title' => $option['label'],
+        '#description' => [
+          '#theme' => 'item_list',
+          '#items' => $unique_definitions[$new_storage_type][$option_key]['description'],
+        ],
+        '#id' => $option['unique_identifier'],
+        '#weight' => $option['weight'],
+        '#parents' => ['group_field_options_wrapper'],
+        '#attributes' => [
+          'class' => ['field-option-radio'],
+          'data-once' => 'field-click-to-select',
+        ],
+        '#wrapper_attributes' => [
+          'class' => ['js-click-to-select', 'subfield-option'],
+        ],
+        '#variant' => 'field-suboption',
+      ];
+      $radio_element['#return_value'] = $option['unique_identifier'];
+      if ((string) $option['unique_identifier'] === 'entity_reference') {
+        $radio_element['#title'] = 'Other';
+        $radio_element['#weight'] = 10;
+      }
+      $group_field_options[$option['unique_identifier']] = $radio_element;
+    }
+    uasort($group_field_options, [SortArray::class, 'sortByWeightProperty']);
+    $form['group_field_options_wrapper']['fields'] += $group_field_options;
+  }
+
+  /**
+   * Adds ungrouped field types and field type groups to the form.
+   *
+   * When a group is selected, the related fields are shown when the form is
+   * rebuilt.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function addGroupFieldOptions(array &$form, FormStateInterface $form_state): void {
+    $field_type_options = $form_state->get('field_type_options');
     $field_type_options_radios = [];
     foreach ($field_type_options as $id => $field_type) {
       /** @var  \Drupal\Core\Field\FieldTypeCategoryInterface $category_info */
       $category_info = $this->fieldTypeCategoryManager->createInstance($field_type['category'], $field_type);
-      $display_as_group = !($category_info instanceof FallbackFieldTypeCategory);
+      $display_as_group = $field_type['display_as_group'];
       $cleaned_class_name = Html::getClass($field_type['unique_identifier']);
       $field_type_options_radios[$id] = [
         '#type' => 'container',
@@ -204,10 +314,6 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
             ],
           ],
         ],
-        // Store some data we later need.
-        '#data' => [
-          '#group_display' => $display_as_group,
-        ],
         'radio' => [
           '#type' => 'radio',
           '#title' => $category_info->getLabel(),
@@ -221,13 +327,6 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
           '#attributes' => [
             'class' => ['field-option-radio'],
           ],
-          '#ajax' => [
-            'callback' => [$this, 'showFieldsCallback'],
-            'event' => 'updateOptions',
-            'wrapper' => 'group-field-options-wrapper',
-            'progress' => 'none',
-            'disable-refocus' => TRUE,
-          ],
           '#description' => [
             '#type' => 'container',
             '#attributes' => [
@@ -244,7 +343,21 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
       }
     }
     uasort($field_type_options_radios, [SortArray::class, 'sortByWeightProperty']);
+
+    $form['add-label'] = [
+      '#type' => 'label',
+      '#title' => $this->t('Choose a type of field'),
+      '#required' => TRUE,
+    ];
+
+    $form['add'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => 'add-field-container',
+      ],
+    ];
     $form['add']['new_storage_type'] = $field_type_options_radios;
+
     $form['group_submit'] = [
       '#type' => 'submit',
       '#value' => $this->t('Change field group'),
@@ -252,135 +365,72 @@ public function buildForm(array $form, FormStateInterface $form_state, $entity_t
       '#attributes' => [
         'class' => ['js-hide'],
       ],
-      '#submit' => [[static::class, 'showFieldsHandler']],
-    ];
-    $form['group_field_options_wrapper'] = [
-      '#prefix' => '<div id="group-field-options-wrapper" class="group-field-options-wrapper">',
-      '#suffix' => '</div>',
+      '#submit' => [[static::class, 'rebuildWithOptions']],
     ];
 
-    // Set the selected field to the form state by checking
-    // the checked attribute.
-    $selected_field_type = NULL;
-    foreach ($field_type_options_radios as $field_type_options_radio) {
-      if ($field_type_options_radio['#attributes']['checked']) {
-        $selected_field_type = $field_type_options_radio['radio']['#return_value'];
-        $form_state->setValue('selected_field_type', $selected_field_type);
-        break;
-      }
-    }
-    if (isset($selected_field_type)) {
-      $group_display = $field_type_options_radios[$selected_field_type]['#data']['#group_display'];
-      if ($group_display) {
-        $form['group_field_options_wrapper']['label'] = [
-          '#type' => 'label',
-          '#title' => t('Choose an option below'),
-          '#required' => TRUE,
-        ];
-        $form['group_field_options_wrapper']['fields'] = [
-          '#type' => 'container',
-          '#attributes' => [
-            'class' => ['group-field-options'],
-          ],
-        ];
+    $form['actions']['submit']['#validate'][] = '::validateGroupOrField';
+    $form['actions']['submit']['#submit'][] = '::rebuildWithOptions';
+  }
 
-        foreach ($unique_definitions[$selected_field_type] as $option_key => $option) {
-          $radio_element = [
-            '#type' => 'radio',
-            '#theme_wrappers' => ['form_element__new_storage_type'],
-            '#title' => $option['label'],
-            '#description' => [
-              '#theme' => 'item_list',
-              '#items' => $unique_definitions[$selected_field_type][$option_key]['description'],
-            ],
-            '#id' => $option['unique_identifier'],
-            '#weight' => $option['weight'],
-            '#parents' => ['group_field_options_wrapper'],
-            '#attributes' => [
-              'class' => ['field-option-radio'],
-              'data-once' => 'field-click-to-select',
-            ],
-            '#wrapper_attributes' => [
-              'class' => ['js-click-to-select', 'subfield-option'],
-            ],
-            '#variant' => 'field-suboption',
-          ];
-          $radio_element['#return_value'] = $option['unique_identifier'];
-          if ((string) $option['unique_identifier'] === 'entity_reference') {
-            $radio_element['#title'] = 'Other';
-            $radio_element['#weight'] = 10;
-          }
-          $group_field_options[$option['unique_identifier']] = $radio_element;
+  /**
+   * Save field type definitions and categories in the form state.
+   *
+   * Get all field type definitions and store each one twice:
+   * - field_type_options: each field type is indexed by its category plugin ID
+   *   or its label.
+   * - unique_definitions: each field type is indexed by its category and name.
+   *
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function processFieldDefinitions(FormStateInterface $form_state): void {
+    $field_type_options = $unique_definitions = [];
+    $grouped_definitions = $this->fieldTypePluginManager->getGroupedDefinitions($this->fieldTypePluginManager->getEntityTypeUiDefinitions($this->entityTypeId), 'label', 'id');
+    // Invoke a hook to get category properties.
+    foreach ($grouped_definitions as $category => $field_types) {
+      foreach ($field_types as $name => $field_type) {
+        $definition = ['unique_identifier' => $name] + $field_type;
+        $category_info = $this->fieldTypeCategoryManager
+          ->createInstance($field_type['category'], $definition);
+        $definition['display_as_group'] = !($category_info instanceof FallbackFieldTypeCategory);
+        if ($this->fieldTypeCategoryManager->hasDefinition($category)) {
+          $id = $category_info->getPluginId();
+        }
+        else {
+          $id = (string) $field_type['label'];
         }
-        uasort($group_field_options, [SortArray::class, 'sortByWeightProperty']);
-        $form['group_field_options_wrapper']['fields'] += $group_field_options;
+        $field_type_options[$id] = $definition;
+        $unique_definitions[$category][$name] = $definition;
       }
     }
-    $field_prefix = $this->config('field_ui.settings')->get('field_prefix');
-    $form['new_storage_wrapper']['field_name'] = [
-      '#type' => 'machine_name',
-      '#field_prefix' => $field_prefix,
-      '#size' => 15,
-      '#description' => $this->t('A unique machine-readable name containing letters, numbers, and underscores.'),
-      // Calculate characters depending on the length of the field prefix
-      // setting. Maximum length is 32.
-      '#maxlength' => FieldStorageConfig::NAME_MAX_LENGTH - strlen($field_prefix),
-      '#machine_name' => [
-        'source' => ['new_storage_wrapper', 'label'],
-        'exists' => [$this, 'fieldNameExists'],
-      ],
-      '#required' => FALSE,
-    ];
-    // Place the 'translatable' property as an explicit value so that contrib
-    // modules can form_alter() the value for newly created fields. By default
-    // we create field storage as translatable so it will be possible to enable
-    // translation at field level.
-    $form['translatable'] = [
-      '#type' => 'value',
-      '#value' => TRUE,
-    ];
-
-    $form['actions'] = ['#type' => 'actions'];
-    $form['actions']['submit'] = [
-      '#type' => 'submit',
-      '#value' => $this->t('Continue'),
-      '#button_type' => 'primary',
-    ];
 
-    $form['#attached']['library'] = [
-      'field_ui/drupal.field_ui',
-      'field_ui/drupal.field_ui.manage_fields',
-      'core/drupal.ajax',
-    ];
-    return $form;
+    $form_state->set('field_type_options', $field_type_options);
+    $form_state->set('unique_definitions', $unique_definitions);
   }
 
   /**
-   * {@inheritdoc}
+   * Validates the first step of the form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
    */
-  public function validateForm(array &$form, FormStateInterface $form_state) {
+  public function validateGroupOrField(array &$form, FormStateInterface $form_state) {
     if (!$form_state->getValue('new_storage_type')) {
-      $form_state->setErrorByName('new_storage_type', $this->t('You need to select a field type.'));
-    }
-    elseif (isset($form['group_field_options_wrapper']['fields']) && !$form_state->getValue('group_field_options_wrapper')) {
-      $form_state->setErrorByName('group_field_options_wrapper', $this->t('You need to select a field type.'));
+      $form_state->setErrorByName('add', $this->t('You need to select a field type.'));
     }
-
-    $this->validateAddNew($form, $form_state);
   }
 
   /**
-   * Validates the 'add new field' case.
+   * Validates the second step (field storage selection and label) of the form.
    *
    * @param array $form
    *   An associative array containing the structure of the form.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The current state of the form.
-   *
-   * @see \Drupal\field_ui\Form\FieldStorageAddForm::validateForm()
    */
-  protected function validateAddNew(array $form, FormStateInterface $form_state) {
-    // Validate if any information was provided in the 'add new field' case.
+  public function validateAddNew(array $form, FormStateInterface $form_state) {
     // Missing label.
     if (!$form_state->getValue('label')) {
       $form_state->setErrorByName('label', $this->t('Add new field: you need to provide a label.'));
@@ -397,6 +447,10 @@ protected function validateAddNew(array $form, FormStateInterface $form_state) {
       $field_name = $this->configFactory->get('field_ui.settings')->get('field_prefix') . $field_name;
       $form_state->setValueForElement($form['new_storage_wrapper']['field_name'], $field_name);
     }
+
+    if (isset($form['group_field_options_wrapper']['fields']) && !$form_state->getValue('group_field_options_wrapper')) {
+      $form_state->setErrorByName('group_field_options_wrapper', $this->t('You need to choose an option.'));
+    }
   }
 
   /**
@@ -554,16 +608,17 @@ public function fieldNameExists($value, $element, FormStateInterface $form_state
   }
 
   /**
-   * Callback for displaying fields after a group has been selected.
+   * Submit handler for displaying fields after a group is selected.
    */
-  public function showFieldsCallback($form, FormStateInterface &$form_state) {
-    return $form['group_field_options_wrapper'];
+  public static function rebuildWithOptions($form, FormStateInterface &$form_state) {
+    $form_state->setRebuild();
   }
 
   /**
-   * Submit handler for displaying fields after a group is selected.
+   * Submit handler for resetting the form.
    */
-  public static function showFieldsHandler($form, FormStateInterface &$form_state) {
+  public static function startOver($form, FormStateInterface &$form_state) {
+    $form_state->unsetValue('new_storage_type');
     $form_state->setRebuild();
   }
 
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php
index eb2da05b3eae..aed84bcc2859 100644
--- a/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsFunctionalTest.php
@@ -115,23 +115,27 @@ public function testDisallowedFieldNames() {
     $this->config('field_ui.settings')->set('field_prefix', '')->save();
 
     $label = 'Disallowed field';
-    $edit = [
-      'label' => $label,
+    $edit1 = [
       'new_storage_type' => 'test_field',
     ];
+    $edit2 = [
+      'label' => $label,
+    ];
 
     // Try with an entity key.
-    $edit['field_name'] = 'title';
+    $edit2['field_name'] = 'title';
     $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
     $this->drupalGet("{$bundle_path}/fields/add-field");
-    $this->submitForm($edit, 'Continue');
+    $this->submitForm($edit1, 'Continue');
+    $this->submitForm($edit2, 'Continue');
     $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
 
     // Try with a base field.
-    $edit['field_name'] = 'sticky';
+    $edit2['field_name'] = 'sticky';
     $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
     $this->drupalGet("{$bundle_path}/fields/add-field");
-    $this->submitForm($edit, 'Continue');
+    $this->submitForm($edit1, 'Continue');
+    $this->submitForm($edit2, 'Continue');
     $this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
   }
 
@@ -219,9 +223,13 @@ public function testHiddenFields() {
             ->elementExists('css', "[name='new_storage_type'][value='$field_type']");
         }
         catch (ElementNotFoundException) {
-          if ($this->getFieldFromGroup($field_type)) {
+          if ($group = $this->getFieldFromGroup($field_type)) {
+            $this->assertSession()
+              ->elementExists('css', "[name='new_storage_type'][value='$group']");
+            $this->submitForm(['new_storage_type' => $group], 'Continue');
             $this->assertSession()
               ->elementExists('css', "[name='group_field_options_wrapper'][value='$field_type']");
+            $this->submitForm([], 'Back');
           }
         }
       }
@@ -239,10 +247,13 @@ public function testDuplicateFieldName() {
     // create a new field with the same name.
     $url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
     $this->drupalGet($url);
+    $edit = [
+      'new_storage_type' => 'boolean',
+    ];
+    $this->submitForm($edit, 'Continue');
     $edit = [
       'label' => $this->randomMachineName(),
       'field_name' => 'tags',
-      'new_storage_type' => 'boolean',
     ];
     $this->submitForm($edit, 'Continue');
 
@@ -384,12 +395,16 @@ public function testFieldPrefix() {
     $field_exceed_max_length_input = $this->randomMachineName(23);
 
     // Try to create the field.
-    $edit = [
+    $edit1 = [
+      'new_storage_type' => 'test_field',
+    ];
+    $edit2 = [
       'label' => $field_exceed_max_length_label,
       'field_name' => $field_exceed_max_length_input,
     ];
     $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
-    $this->submitForm($edit, 'Continue');
+    $this->submitForm($edit1, 'Continue');
+    $this->submitForm($edit2, 'Continue');
     $this->assertSession()->pageTextContains('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
 
     // Create a valid field.
diff --git a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
index 8bd98ebe26f9..35d8d9ad21dd 100644
--- a/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
+++ b/core/modules/field_ui/tests/src/Functional/ManageFieldsTest.php
@@ -142,10 +142,13 @@ public function testAddField() {
     $this->assertNull(FieldStorageConfig::loadByName('node', "field_test_field"));
 
     $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
+    $edit = [
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
     $edit = [
       'label' => 'Test field',
       'field_name' => 'test_field',
-      'new_storage_type' => 'test_field',
     ];
     $this->submitForm($edit, 'Continue');
     $this->assertSession()->statusMessageNotContains('Saved');
@@ -160,10 +163,13 @@ public function testAddField() {
 
     // Try creating a field with the same machine name.
     $this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
+    $edit = [
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
     $edit = [
       'label' => 'Test field',
       'field_name' => 'test_field',
-      'new_storage_type' => 'test_field',
     ];
     $this->submitForm($edit, 'Continue');
     // Assert that the values in the field storage form are reset.
@@ -193,10 +199,13 @@ public function testAddFieldWithMultipleUsers() {
     // Start adding a field as user 1, stop prior to saving, but keep the URL.
     $this->drupalLogin($user1);
     $this->drupalGet($bundle_path . '/fields/add-field');
+    $edit = [
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
     $edit = [
       'label' => 'Test field',
       'field_name' => 'test_field',
-      'new_storage_type' => 'test_field',
     ];
     $this->submitForm($edit, 'Continue');
     // Make changes to the storage form.
@@ -208,10 +217,13 @@ public function testAddFieldWithMultipleUsers() {
     // Actually add a field as user 2.
     $this->drupalLogin($user2);
     $this->drupalGet($bundle_path . '/fields/add-field');
+    $edit = [
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
     $edit = [
       'label' => 'Test field',
       'field_name' => 'test_field',
-      'new_storage_type' => 'test_field',
     ];
     $this->submitForm($edit, 'Continue');
     $allowed_no_of_values = $page->findField('field_storage[subform][cardinality_number]')->getValue();
@@ -246,10 +258,13 @@ public function testEditFieldWithLeftOverFieldInTempStore() {
     // Start adding a field but stop prior to saving.
     $this->drupalLogin($user);
     $this->drupalGet($bundle_path . '/fields/add-field');
+    $edit = [
+      'new_storage_type' => 'test_field',
+    ];
+    $this->submitForm($edit, 'Continue');
     $edit = [
       'label' => 'Test field',
       'field_name' => 'test_field',
-      'new_storage_type' => 'test_field',
     ];
     $this->submitForm($edit, 'Continue');
 
diff --git a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php
index a33d124dee84..36fca849dc6f 100644
--- a/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php
+++ b/core/modules/field_ui/tests/src/FunctionalJavascript/ManageFieldsTest.php
@@ -179,38 +179,40 @@ public function testAddField() {
     $assert_session = $this->assertSession();
 
     $this->drupalGet('admin/structure/types/manage/article/fields/add-field');
-    $field_name = 'test_field_1';
-    $page->fillField('label', $field_name);
 
     // Test validation.
     $page->pressButton('Continue');
     $assert_session->pageTextContains('You need to select a field type.');
-    $assert_session->elementExists('css', '[name="new_storage_type"].error');
     $assert_session->pageTextNotContains('Choose an option below');
 
     $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent());
     $number_field->click();
-    $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected());
+    $page->pressButton('Continue');
     $assert_session->pageTextContains('Choose an option below');
+    $field_name = 'test_field_1';
+    $page->fillField('label', $field_name);
     $page->pressButton('Continue');
-    $assert_session->pageTextContains('You need to select a field type.');
+    $assert_session->pageTextContains('You need to choose an option.');
     $assert_session->elementNotExists('css', '[name="new_storage_type"].error');
     $assert_session->elementExists('css', '[name="group_field_options_wrapper"].error');
+    $page->pressButton('Back');
 
     // Try adding a field using a grouped field type.
     $this->assertNotEmpty($email_field = $page->find('xpath', '//*[text() = "Email"]')->getParent());
     $email_field->click();
-    $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="email"]')->isSelected());
+    $page->pressButton('Continue');
     $assert_session->pageTextNotContains('Choose an option below');
+    $page->pressButton('Back');
 
     $this->assertNotEmpty($text = $page->find('xpath', '//*[text() = "Plain text"]')->getParent());
     $text->click();
-    $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="plain_text"]')->isSelected());
+    $page->pressButton('Continue');
     $assert_session->pageTextContains('Choose an option below');
 
+    $page->fillField('label', $field_name);
     $this->assertNotEmpty($text_plain = $page->find('xpath', '//*[text() = "Text (plain)"]')->getParent());
     $text_plain->click();
     $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="string"]')->isSelected());
@@ -265,22 +267,23 @@ public function testAddField() {
 
     // Try adding a field using a non-grouped field type.
     $this->drupalGet('admin/structure/types/manage/article/fields/add-field');
-    $field_name = 'test_field_2';
-    $page->fillField('label', $field_name);
 
     $this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent());
     $number_field->click();
-    $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="number"]')->isSelected());
+    $page->pressButton('Continue');
     $assert_session->pageTextContains('Choose an option below');
     $this->assertNotEmpty($number_integer = $page->find('xpath', '//*[text() = "Number (integer)"]')->getParent());
     $number_integer->click();
     $this->assertTrue($assert_session->elementExists('css', '[name="group_field_options_wrapper"][value="integer"]')->isSelected());
 
+    $page->pressButton('Back');
     $this->assertNotEmpty($test_field = $page->find('xpath', '//*[text() = "Test field"]')->getParent());
     $test_field->click();
-    $assert_session->assertWaitOnAjaxRequest();
     $this->assertTrue($assert_session->elementExists('css', '[name="new_storage_type"][value="test_field"]')->isSelected());
+    $page->pressButton('Continue');
+    $field_name = 'test_field_2';
+    $page->fillField('label', $field_name);
     $assert_session->pageTextNotContains('Choose an option below');
 
     $page->pressButton('Continue');
@@ -305,7 +308,7 @@ public function testFieldTypeOrder() {
       // Select the group card.
       $group_field_card = $page->find('css', "[name='new_storage_type'][value='$field_type_category']")->getParent();
       $group_field_card->click();
-      $this->assertSession()->assertWaitOnAjaxRequest();
+      $page->pressButton('Continue');
       $field_types = $page->findAll('css', '.subfield-option .option');
       $field_type_labels = [];
       foreach ($field_types as $field_type) {
@@ -325,6 +328,8 @@ public function testFieldTypeOrder() {
       };
       // Assert that the field type options are displayed as per their weights.
       $this->assertSame($expected_field_types, $field_type_labels);
+      // Return to the first step of the form.
+      $page->pressButton('Back');
     }
   }
 
@@ -356,10 +361,15 @@ public function testAllowedValuesFormValidation() {
   public function testLabelFieldFormValidation() {
     $this->drupalGet('/admin/structure/types/manage/article/fields/add-field');
     $page = $this->getSession()->getPage();
-    $page->findButton('Continue')->click();
 
-    $this->assertSession()->pageTextContains('You need to provide a label.');
+    $page->findButton('Continue')->click();
     $this->assertSession()->pageTextContains('You need to select a field type.');
+
+    $this->assertNotEmpty($boolean_field = $page->find('xpath', '//*[text() = "Boolean (overridden by alter)"]')->getParent());
+    $boolean_field->click();
+    $page->findButton('Continue')->click();
+    $page->findButton('Continue')->click();
+    $this->assertSession()->pageTextContains('Add new field: you need to provide a label.');
   }
 
 }
diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php
index cba90a5bb4bb..c611e00adede 100644
--- a/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php
+++ b/core/modules/field_ui/tests/src/Traits/FieldUiJSTestTrait.php
@@ -51,6 +51,7 @@ public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ?
       $field_card = $this->getFieldFromGroupJS($field_type);
     }
     $field_card?->click();
+    $page->findButton('Continue')->click();
     $field_label = $page->findField('edit-label');
     $this->assertTrue($field_label->isVisible());
     $field_label = $page->find('css', 'input[data-drupal-selector="edit-label"]');
@@ -140,11 +141,12 @@ public function getFieldFromGroupJS($field_type) {
     foreach ($groups as $group) {
       $group_field_card = $this->getSession()->getPage()->find('css', "[name='new_storage_type'][value='$group']")->getParent();
       $group_field_card->click();
-      $this->assertSession()->assertWaitOnAjaxRequest();
+      $this->getSession()->getPage()->pressButton('Continue');
       $field_card = $this->getSession()->getPage()->find('css', "[name='group_field_options_wrapper'][value='$field_type']");
       if ($field_card) {
         break;
       }
+      $this->getSession()->getPage()->pressButton('Back');
     }
     return $field_card->getParent();
   }
diff --git a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
index 87e835f7cead..5a86e52f846f 100644
--- a/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
+++ b/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
@@ -38,7 +38,13 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
     // test failure.
     // See https://www.drupal.org/project/drupal/issues/3030902
     $label = $label ?: $this->randomMachineName();
-    $initial_edit = [];
+    $initial_edit = [
+      'new_storage_type' => $field_type,
+    ];
+    $second_edit = [
+      'label' => $label,
+      'field_name' => $field_name,
+    ];
 
     // Allow the caller to set a NULL path in case they navigated to the right
     // page before calling this method.
@@ -54,12 +60,6 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
     try {
       // First check if the passed in field type is not part of a group.
       $this->assertSession()->elementExists('css', "[name='new_storage_type'][value='$field_type']");
-      // If the element exists then we can add it to our object.
-      $initial_edit = [
-        'new_storage_type' => $field_type,
-        'label' => $label,
-        'field_name' => $field_name,
-      ];
     }
     // If the element could not be found then it is probably in a group.
     catch (ElementNotFoundException) {
@@ -67,18 +67,13 @@ public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $fi
       $field_group = $this->getFieldFromGroup($field_type);
       if ($field_group) {
         // Pass in the group name as the new storage type.
-        $selected_group = [
-          'new_storage_type' => $field_group,
-        ];
-        $this->submitForm($selected_group, 'Change field group');
-        $initial_edit = [
-          'group_field_options_wrapper' => $field_type,
-          'label' => $label,
-          'field_name' => $field_name,
-        ];
+        $initial_edit['new_storage_type'] = $field_group;
+        $second_edit['group_field_options_wrapper'] = $field_type;
+        $this->drupalGet($bundle_path);
       }
     }
     $this->submitForm($initial_edit, 'Continue');
+    $this->submitForm($second_edit, 'Continue');
     // Assert that the field is not created.
     $this->assertFieldDoesNotExist($bundle_path, $label);
     if ($save_settings) {
@@ -208,12 +203,14 @@ public function getFieldFromGroup($field_type) {
       $test = [
         'new_storage_type' => $group,
       ];
-      $this->submitForm($test, 'Change field group');
+      $this->submitForm($test, 'Continue');
       try {
         $this->assertSession()->elementExists('css', "[name='group_field_options_wrapper'][value='$field_type']");
+        $this->submitForm([], 'Back');
         return $group;
       }
       catch (ElementNotFoundException) {
+        $this->submitForm([], 'Back');
         continue;
       }
     }
diff --git a/core/modules/media/media.module b/core/modules/media/media.module
index 595389ab8476..b0ba7843f199 100644
--- a/core/modules/media/media.module
+++ b/core/modules/media/media.module
@@ -208,7 +208,7 @@ function media_form_field_ui_field_storage_add_form_alter(&$form, FormStateInter
     'file_upload',
     'field_ui:entity_reference:media',
   ];
-  if (in_array($form_state->getValue('selected_field_type'), $field_types)) {
+  if (in_array($form_state->getValue('new_storage_type'), $field_types)) {
     $form['group_field_options_wrapper']['description_wrapper'] = [
       '#type' => 'item',
       '#markup' => $description_text,
diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php
index 2b351a002fe9..4f1cf6d2bf5a 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/MediaReferenceFieldHelpTest.php
@@ -49,8 +49,9 @@ public function testFieldCreationHelpText() {
     // visible.
     $assert_session->elementExists('css', "[name='new_storage_type'][value='boolean']");
     $page->find('css', "[name='new_storage_type'][value='boolean']")->getParent()->click();
-    $assert_session->assertWaitOnAjaxRequest();
+    $page->pressButton('Continue');
     $assert_session->pageTextNotContains($help_text);
+    $page->pressButton('Back');
 
     // Select each of the Reference, File upload field groups and verify their
     // descriptions are now visible and match the expected text.
@@ -58,8 +59,9 @@ public function testFieldCreationHelpText() {
       $assert_session->elementExists('css', "[name='new_storage_type'][value='$field_group']");
       $page->find('css', "[name='new_storage_type'][value='$field_group']")->getParent()->click();
 
-      $assert_session->assertWaitOnAjaxRequest();
+      $page->pressButton('Continue');
       $assert_session->pageTextContains($help_text);
+      $page->pressButton('Back');
     }
   }
 
diff --git a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php
index c2584dfcbe4b..f1f08d1f4542 100644
--- a/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php
+++ b/core/modules/media_library/tests/src/FunctionalJavascript/FieldUiIntegrationTest.php
@@ -69,6 +69,7 @@ public function testFieldUiIntegration() {
 
     $this->drupalGet('/admin/structure/types/manage/article/fields/add-field');
     $page->find('css', "[name='new_storage_type'][value='field_ui:entity_reference:media']")->getParent()->click();
+    $page->findButton('Continue')->click();
     $this->assertNotNull($assert_session->waitForField('label'));
     $page->fillField('label', 'Shatner');
     $this->waitForText('field_shatner');
diff --git a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php
index 043cdb80b4d5..0f6a7149cc26 100644
--- a/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php
+++ b/core/modules/node/tests/src/Functional/NodeTypeTranslationTest.php
@@ -170,6 +170,8 @@ public function testNodeTypeTitleLabelTranslation() {
     $this->drupalGet("admin/structure/types/manage/{$type}/fields/add-field");
     $this->submitForm([
       'new_storage_type' => 'email',
+    ], 'Continue');
+    $this->submitForm([
       'label' => 'Email',
       'field_name' => 'email',
     ], 'Continue');
-- 
GitLab