diff --git a/src/Context/ContextDefinition.php b/src/Context/ContextDefinition.php
index 7122d6ec..115e3dce 100644
--- a/src/Context/ContextDefinition.php
+++ b/src/Context/ContextDefinition.php
@@ -43,6 +43,13 @@ class ContextDefinition extends ContextDefinitionCore implements ContextDefiniti
    */
   protected $assignmentRestriction = NULL;
 
+  /**
+   * An array of data type => widget id pairs.
+   *
+   * @var array
+   */
+  protected $widgetList;
+
   /**
    * {@inheritdoc}
    */
@@ -117,4 +124,80 @@ class ContextDefinition extends ContextDefinitionCore implements ContextDefiniti
     return $this;
   }
 
+  /**
+   * An array of standard data types and their corresponding widget id.
+   *
+   * This covers all the data types provided by Core and Typed Data API
+   * Enhancements, apart from 'binary' and 'map'.
+   *
+   * @return array
+   *   An array of data type => widget id.
+   */
+  protected static function getStandardWidgetList() {
+    return [
+      // Single line text input.
+      'any' => 'text_input',
+      'boolean' => 'text_input',
+      'email' => 'text_input',
+      'entity' => 'text_input',
+      'entity_reference' => 'text_input',
+      'float' => 'text_input',
+      'integer' => 'text_input',
+      'language' => 'text_input',
+      'language_reference' => 'text_input',
+      'string' => 'text_input',
+      'uri' => 'text_input',
+
+      // Multi-line text area.
+      'list' => 'textarea',
+      'text' => 'textarea',
+
+      // Date.
+      'datetime_iso8601' => 'datetime',
+      'timestamp' => 'datetime',
+
+      // Date range.
+      'duration_iso8601' => 'datetime_range',
+      'timespan' => 'datetime_range',
+
+      // Selection list.
+      'none-yet' => 'select',
+    ];
+  }
+
+  /**
+   * Derive a widget id from a datatype.
+   *
+   * The widget id is used to generate a form element provided by Typed Data.
+   * Modules can implement hook_typed_data_widgetlist_alter() to declare which
+   * widget to use for their custom data types.
+   *
+   * @todo this function should be moved into the typed_data module. But whilst
+   * still developing the integration of widgets in Rules it can stay here.
+   * Inject $this->moduleHandler instead of calling \Drupal::moduleHandler() ?
+   *
+   * @param string $dataType
+   *   The datatype to check.
+   *
+   * @return string
+   *   The id of the widget to use when building the input form.
+   */
+  public function getWidgetId($dataType) {
+    if (!isset($this->widgetList)) {
+      $this->widgetList = static::getStandardWidgetList();
+      // Allow other modules to add to the datatype -> widget id list by
+      // invoking all hook_typed_data_widgetlist_alter() implementations.
+      \Drupal::moduleHandler()->alter('typed_data_widgetlist', $this->widgetList);
+    }
+    // Return the widget id for this data type. If none, default to 'broken' id.
+    return isset($this->widgetList[$dataType]) ? $this->widgetList[$dataType] : 'broken';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidgetSettings() {
+    return [];
+  }
+
 }
diff --git a/src/Context/ContextDefinitionInterface.php b/src/Context/ContextDefinitionInterface.php
index db91a049..16d90ad7 100644
--- a/src/Context/ContextDefinitionInterface.php
+++ b/src/Context/ContextDefinitionInterface.php
@@ -70,4 +70,24 @@ interface ContextDefinitionInterface extends ContextDefinitionInterfaceCore {
    */
   public function toArray();
 
+  /**
+   * Gets the widget id for a data type.
+   *
+   * @param string $dataType
+   *   The data type of the field.
+   *
+   * @return string
+   *   A string with the widget id. Will return 'broken' if the data type needed
+   *   by the context is not supported by any widget.
+   */
+  public function getWidgetId($dataType);
+
+  /**
+   * Gets default configuration of the widget.
+   *
+   * @return array
+   *   An associative array with the default configuration.
+   */
+  public function getWidgetSettings();
+
 }
diff --git a/src/Context/ContextHandlerIntegrityTrait.php b/src/Context/ContextHandlerIntegrityTrait.php
index 31b4c231..63507139 100644
--- a/src/Context/ContextHandlerIntegrityTrait.php
+++ b/src/Context/ContextHandlerIntegrityTrait.php
@@ -129,26 +129,32 @@ trait ContextHandlerIntegrityTrait {
    *   The list of violations where new ones will be added.
    */
   protected function checkDataTypeCompatible(CoreContextDefinitionInterface $context_definition, DataDefinitionInterface $provided, $context_name, IntegrityViolationList $violation_list) {
-    // Compare data types. For now, fail if they are not equal.
-    // @todo Add support for matching based upon type-inheritance.
-    $target_type = $context_definition->getDataDefinition()->getDataType();
+    $expected_type = $context_definition->getDataDefinition()->getDataType();
+    $provided_type = $provided->getDataType();
+    if ($expected_type == $provided_type) {
+      return;
+    }
 
-    // Special case any and entity target types for now.
-    if ($target_type == 'any' || ($target_type == 'entity' && strpos($provided->getDataType(), 'entity:') !== FALSE)) {
+    // Make a special case for 'any' and 'entity' expected types for now.
+    if ($expected_type == 'any' || ($expected_type == 'entity' && strpos($provided_type, 'entity:') !== FALSE)) {
       return;
     }
-    if ($target_type != $provided->getDataType()) {
-      $expected_type_problem = $context_definition->getDataDefinition()->getDataType();
-      $violation = new IntegrityViolation();
-      $violation->setMessage($this->t('Expected a @expected_type data type for context %context_name but got a @provided_type data type instead.', [
-        '@expected_type' => $expected_type_problem,
-        '%context_name' => $context_definition->getLabel(),
-        '@provided_type' => $provided->getDataType(),
-      ]));
-      $violation->setContextName($context_name);
-      $violation->setUuid($this->getUuid());
-      $violation_list->add($violation);
+
+    $provided_class = $provided->getClass();
+    $expected_class = $context_definition->getDataDefinition()->getClass();
+    if (is_subclass_of($expected_class, $provided_class)) {
+      return;
     }
+
+    $violation = new IntegrityViolation();
+    $violation->setMessage($this->t('Expected a %allowed_type data type for context %context_name but got a %provided_type data type instead.', [
+      '%allowed_type' => $expected_type,
+      '%context_name' => $context_definition->getLabel(),
+      '%provided_type' => $provided->getDataType(),
+    ]));
+    $violation->setContextName($context_name);
+    $violation->setUuid($this->getUuid());
+    $violation_list->add($violation);
   }
 
 }
diff --git a/src/Context/ContextHandlerTrait.php b/src/Context/ContextHandlerTrait.php
index 37ccb908..a8b2591e 100644
--- a/src/Context/ContextHandlerTrait.php
+++ b/src/Context/ContextHandlerTrait.php
@@ -267,10 +267,12 @@ trait ContextHandlerTrait {
 
     // Reverse the mapping and apply the changes.
     foreach ($changed_definitions as $context_name => $definition) {
-      $selector = $this->configuration['context_mapping'][$context_name];
-      // @todo Deal with selectors matching not a context name.
-      if (strpos($selector, '.') === FALSE) {
-        $metadata_state->setDataDefinition($selector, $definition);
+      if (isset($this->configuration['context_mapping'])) {
+        $selector = $this->configuration['context_mapping'][$context_name];
+        // @todo Deal with selectors matching not a context name.
+        if (strpos($selector, '.') === FALSE) {
+          $metadata_state->setDataDefinition($selector, $definition);
+        }
       }
     }
   }
diff --git a/src/Context/Form/ContextFormTrait.php b/src/Context/Form/ContextFormTrait.php
index d85c7dec..aab288b2 100644
--- a/src/Context/Form/ContextFormTrait.php
+++ b/src/Context/Form/ContextFormTrait.php
@@ -6,25 +6,20 @@ use Drupal\Core\Form\FormStateInterface;
 use Drupal\rules\Context\ContextConfig;
 use Drupal\rules\Context\ContextDefinitionInterface;
 use Drupal\rules\Context\DataProcessorManagerTrait;
+use Drupal\typed_data\Form\SubformState;
+use Drupal\typed_data\Widget\FormWidgetManagerTrait;
 
 /**
  * Provides form logic for handling contexts when configuring an expression.
  */
 trait ContextFormTrait {
   use DataProcessorManagerTrait;
+  use FormWidgetManagerTrait;
 
   /**
    * Provides the form part for a context parameter.
    */
   public function buildContextForm(array $form, FormStateInterface $form_state, $context_name, ContextDefinitionInterface $context_definition, array $configuration) {
-    $form['context_definitions'][$context_name] = [
-      '#type' => 'fieldset',
-      '#title' => $context_definition->getLabel(),
-    ];
-    $form['context_definitions'][$context_name]['description'] = [
-      '#markup' => $context_definition->getDescription(),
-    ];
-
     // If the form has been submitted already take the mode from the submitted
     // value, otherwise check for restriction setting, then check existing
     // configuration, and if that does not exist default to the "input" mode.
@@ -44,6 +39,9 @@ trait ContextFormTrait {
 
     $title = $mode == ContextDefinitionInterface::ASSIGNMENT_RESTRICTION_SELECTOR ? $this->t('Data selector') : $this->t('Value');
 
+    // The default value will be an array if the widget has two or more data
+    // input items, for example datespan, or the Rules context definition has
+    // 'multiple = TRUE'. Otherwise it will be a scalar value.
     if (isset($configuration['context_values'][$context_name])) {
       $default_value = $configuration['context_values'][$context_name];
     }
@@ -53,17 +51,84 @@ trait ContextFormTrait {
     else {
       $default_value = $context_definition->getDefaultValue();
     }
-    $form['context_definitions'][$context_name]['setting'] = [
-      '#type' => 'textfield',
-      '#title' => $title,
-      '#required' => $context_definition->isRequired(),
-      '#default_value' => $default_value,
+
+    // Temporary fix: Cast default value to an array if context definition has
+    // 'multiple = TRUE' so that the action can be edited without a fatal error.
+    // @todo Remove when integrity check prevents the wrong type being saved.
+    // @see https://www.drupal.org/project/rules/issues/2723259
+    if ($context_definition->isMultiple() && is_scalar($default_value)) {
+      $default_value = [$default_value];
+    }
+
+    // Derive the widget id using the context definition data type. Take only
+    // the first word before : so that entity:user gives entity.
+    $dataType = explode(':', $context_definition->getDataType())[0];
+    $widget_id = $context_definition->getWidgetId($dataType);
+
+    if ($widget_id == 'broken') {
+      // The datatype is unknown and/or the typed-data widget has not been coded
+      // yet. Use 'broken' which by design has no input field.
+      \Drupal::messenger()->addError($this->t('No form widget is defined for %label (context name %context_name, data type %dataType). Modules can implement hook_typed_data_widgetlist_alter() to declare which form widget to use.', [
+        '%context_name' => $context_name,
+        '%dataType' => $dataType,
+        '%label' => is_object($context_definition->getLabel()) ? $context_definition->getLabel()->getUntranslatedString() : '* no label *',
+      ]));
+    }
+
+    // Create the widget using the widget_id.
+    $widget = $this->getFormWidgetManager()->createInstance($widget_id);
+
+    $dataManager = $context_definition->getTypedDataManager();
+    $dataDefinition = $context_definition->getDataDefinition();
+    $typed_data = $dataManager->create($dataDefinition);
+
+    // Set default before building the form, so that widget->form() can use it.
+    $typed_data->setValue($default_value);
+
+    // Create the widget sub-form.
+    $sub_form = [];
+    $sub_form_state = SubformState::createForSubform($sub_form, $form, $form_state);
+    $widget_form = $widget->form($typed_data, $sub_form_state);
+
+    // Add the widget form into the full $form and save the widget_id.
+    $form['context_definitions'][$context_name] = $widget_form + [
+      '#widget_id' => $widget_id,
     ];
+    $form['context_definitions'][$context_name]['#attributes']['class'][] = 'widget-' . str_replace('_', '-', $widget_id);
+
+    // Get the names of the data item input fields in the widget. Mostly there
+    // is only one input field and often it will be called 'value', but some
+    // datatypes, for example timespan, define two inputs. Remove all array keys
+    // that start with # and this will leave just the input field names.
+    // @todo Could this information be provided more easily, say by calling
+    // $widget->getInputNames() ?
+    $widget_input_names = array_keys($widget_form);
+    $widget_input_names = array_filter($widget_input_names, function ($k) {
+      return substr($k, 0, 1) !== '#';
+    });
+
+    if (isset($widget_form['#theme_wrappers']) && $widget_form['#theme_wrappers'][0] == 'fieldset') {
+      // @todo Is checking #theme_wrappers = fieldset the best way to do it?
+      // Should we count the number of input items? What if the widget had two
+      // or more inputs but was badly formed with no fieldset theme_wrapper?
+      $input_name = end($widget_input_names);
+    }
+    else {
+      // The widget form is simple, so we need to add more of the form here.
+      $form['context_definitions'][$context_name] += [
+        '#type' => 'fieldset',
+        '#title' => $context_definition->getLabel(),
+      ];
+      $input_name = reset($widget_input_names);
+      $form['context_definitions'][$context_name][$input_name]['#title'] = $title;
+    }
 
-    $element = &$form['context_definitions'][$context_name]['setting'];
+    // Extract the (last) element we have just added.
+    $element = &$form['context_definitions'][$context_name][$input_name];
 
     if ($mode == ContextDefinitionInterface::ASSIGNMENT_RESTRICTION_SELECTOR) {
-      $element['#description'] = $this->t("The data selector helps you drill down into the available data. <em>To make entity fields appear in the data selector, you may have to use the condition 'entity has field' (or 'content is of type').</em> More useful tips about data selection is available in <a href=':url'>the online documentation</a>.", [
+      $element['#type'] = 'textfield';
+      $element['#description'] .= ' ' . $this->t("The data selector helps you drill down into the available data. <em>To make entity fields appear in the data selector, you may have to use the condition 'entity has field' (or 'content is of type').</em> More useful tips about data selection is available in <a href=':url'>the online documentation</a>.", [
         ':url' => 'https://www.drupal.org/node/1300042',
       ]);
 
@@ -74,18 +139,18 @@ trait ContextFormTrait {
     }
     elseif ($context_definition->isMultiple()) {
       $element['#type'] = 'textarea';
-      // @todo Get a description for possible values that can be filled in.
-      $element['#description'] = $this->t('Enter one value per line for this multi-valued context.');
+      $element['#description'] .= ' ' . $this->t('Enter one value per line for this multi-valued context.');
 
-      // Glue the list of values together as one item per line in the text area.
+      // Convert the array of values into a single string split by newlines, to
+      // show each item on a separate line in the text area.
       if (is_array($default_value)) {
         $element['#default_value'] = implode("\n", $default_value);
       }
     }
 
-    // If the context is not restricted to one mode or the other then provide a
-    // button to switch between the two modes.
-    if (empty($context_definition->getAssignmentRestriction())) {
+    // If the context is not restricted to one mode or the other, and the widget
+    // is ok (not broken) then provide a button to switch between the two modes.
+    if (empty($context_definition->getAssignmentRestriction()) && $widget_id != 'broken') {
       $value = $mode == ContextDefinitionInterface::ASSIGNMENT_RESTRICTION_SELECTOR ? $this->t('Switch to the direct input mode') : $this->t('Switch to data selection');
       $form['context_definitions'][$context_name]['switch_button'] = [
         '#type' => 'submit',
@@ -132,6 +197,8 @@ trait ContextFormTrait {
   /**
    * Creates a context config object from the submitted form values.
    *
+   * @param array $form
+   *   The complete form array.
    * @param \Drupal\Core\Form\FormStateInterface $form_state
    *   The form state containing the submitted values.
    * @param \Drupal\Core\Plugin\Context\ContextDefinitionInterface[] $context_definitions
@@ -140,27 +207,53 @@ trait ContextFormTrait {
    * @return \Drupal\rules\Context\ContextConfig
    *   The context config object populated with context mappings/values.
    */
-  protected function getContextConfigFromFormValues(FormStateInterface $form_state, array $context_definitions) {
+  protected function getContextConfigFromFormValues(array $form, FormStateInterface $form_state, array $context_definitions) {
     $context_config = ContextConfig::create();
     if ($form_state->hasValue('context_definitions')) {
       foreach ($form_state->getValue('context_definitions') as $context_name => $value) {
+        $context_definition = $context_definitions[$context_name];
+
+        if ($context_definition->isMultiple()) {
+          // Remove the switch button then get the input directly. This is not
+          // the right way. The problem is that Rules uses 'multiple = TRUE' for
+          // textarea to allow multiple entry items. However, they have to be
+          // in an array for the TypedData setValue to work without throwing
+          // InvalidArgumentException: Cannot set a list with a non-array value.
+          // @todo Fix this.
+          // @see https://www.drupal.org/project/typed_data/issues/2847804
+          unset($value['switch_button']);
+          $input = reset($value);
+        }
+        else {
+          // Get the input the 'proper' way.
+          // Create an instance of the widget that was used in this context so
+          // that we can use extractFormValues() to get the entered data.
+          $widget_id = $form['context_definitions'][$context_name]['#widget_id'];
+          $widget = $this->getFormWidgetManager()->createInstance($widget_id);
+          $data = $context_definition->getTypedDataManager()
+            ->create($context_definition->getDataDefinition());
+          $subform_state = SubformState::createWithParents(['context_definitions', $context_name], $form, $form_state);
+          $widget->extractFormValues($data, $subform_state);
+          $input = $data->getValue();
+        }
+
         if ($form_state->get("context_$context_name") == ContextDefinitionInterface::ASSIGNMENT_RESTRICTION_SELECTOR) {
-          $context_config->map($context_name, $value['setting']);
+          $context_config->map($context_name, $input);
         }
         else {
-          // Each line of the textarea is one value for 'multiple' contexts.
-          if ($context_definitions[$context_name]->isMultiple()) {
+          // Not selector, so it must be input.
+          if ($context_definition->isMultiple()) {
             // Textareas should always have \r\n line breaks, but for more
             // robust parsing we should also accommodate just \n or just \r.
-            //
             // Additionally, we want to remove leading and trailing whitespace
             // from each line, and discard any empty lines.
-            $values = preg_split('/\s*\R\s*/', $value['setting'], NULL, PREG_SPLIT_NO_EMPTY);
+            $values = preg_split('/\s*\R\s*/', $input, NULL, PREG_SPLIT_NO_EMPTY);
             $context_config->setValue($context_name, $values);
           }
           else {
-            $context_config->setValue($context_name, $value['setting']);
+            $context_config->setValue($context_name, $input);
           }
+
           // For now, always add in the token context processor if it's present.
           // @todo Improve this in https://www.drupal.org/node/2804035.
           if ($this->getDataProcessorManager()->getDefinition('rules_tokens')) {
@@ -174,7 +267,7 @@ trait ContextFormTrait {
   }
 
   /**
-   * Submit callback: switch a context to data selecor or direct input mode.
+   * Submit callback: switch a context to data selector or direct input mode.
    */
   public static function switchContextMode(array &$form, FormStateInterface $form_state) {
     $element_name = $form_state->getTriggeringElement()['#name'];
diff --git a/src/Form/Expression/ActionForm.php b/src/Form/Expression/ActionForm.php
index 77d105a8..c47a2447 100644
--- a/src/Form/Expression/ActionForm.php
+++ b/src/Form/Expression/ActionForm.php
@@ -158,7 +158,7 @@ class ActionForm implements ExpressionFormInterface {
     }
 
     $action_definition = $this->actionManager->getDefinition($action_id);
-    $context_config = $this->getContextConfigFromFormValues($form_state, $action_definition['context_definitions']);
+    $context_config = $this->getContextConfigFromFormValues($form, $form_state, $action_definition['context_definitions']);
 
     // Rename provided variables, if any.
     if ($provided_variables = $form_state->getValue('provides')) {
diff --git a/src/Form/Expression/ConditionForm.php b/src/Form/Expression/ConditionForm.php
index 95e7ecd1..8e04989a 100644
--- a/src/Form/Expression/ConditionForm.php
+++ b/src/Form/Expression/ConditionForm.php
@@ -172,7 +172,7 @@ class ConditionForm implements ExpressionFormInterface {
     }
 
     $condition_definition = $this->conditionManager->getDefinition($condition_id);
-    $context_config = $this->getContextConfigFromFormValues($form_state, $condition_definition['context_definitions']);
+    $context_config = $this->getContextConfigFromFormValues($form, $form_state, $condition_definition['context_definitions']);
 
     // Rename provided variables, if any.
     if ($provided_variables = $form_state->getValue('provides')) {
diff --git a/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php b/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php
index d19fd3a3..deec39bf 100644
--- a/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php
+++ b/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php
@@ -27,7 +27,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       label = @Translation("Subject"),
  *       description = @Translation("The email's subject.")
  *     ),
- *     "message" = @ContextDefinition("string",
+ *     "message" = @ContextDefinition("text",
  *       label = @Translation("Message"),
  *       description = @Translation("The email's message body. Drupal will by default remove all HTML tags. If you want to use HTML you must override this behavior by installing a contributed module such as Mime Mail.")
  *     ),
diff --git a/src/Plugin/RulesAction/SystemSendEmail.php b/src/Plugin/RulesAction/SystemSendEmail.php
index 5fff718b..602917f4 100644
--- a/src/Plugin/RulesAction/SystemSendEmail.php
+++ b/src/Plugin/RulesAction/SystemSendEmail.php
@@ -26,7 +26,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       label = @Translation("Subject"),
  *       description = @Translation("The email's subject.")
  *     ),
- *     "message" = @ContextDefinition("string",
+ *     "message" = @ContextDefinition("text",
  *       label = @Translation("Message"),
  *       description = @Translation("The email's message body. Drupal will by default remove all HTML tags. If you want to use HTML you must override this behavior by installing a contributed module such as Mime Mail.")
  *     ),
@@ -45,7 +45,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *   }
  * )
  *
- * @todo Define that message Context should be textarea comparing with textfield Subject
  * @todo Add access callback information from Drupal 7.
  */
 class SystemSendEmail extends RulesActionBase implements ContainerFactoryPluginInterface {
diff --git a/tests/src/Functional/ActionsFormTest.php b/tests/src/Functional/ActionsFormTest.php
index a2ea49c2..a7434295 100644
--- a/tests/src/Functional/ActionsFormTest.php
+++ b/tests/src/Functional/ActionsFormTest.php
@@ -90,6 +90,13 @@ class ActionsFormTest extends RulesBrowserTestBase {
     $assert->statusCodeEquals(200);
     $assert->pageTextContains('Edit ' . $action->getLabel());
 
+    // Assert that the fields use the correct widgets, identified by class.
+    if (!empty($widgets)) {
+      foreach ($widgets as $name => $widget_id) {
+        $assert->elementExists('xpath', "//fieldset[@id='edit-context-definitions-$name' and contains(@class, 'widget-$widget_id')]");
+      }
+    }
+
     // If any field values have been specified then fill in the form and save.
     if (!empty($values)) {
 
@@ -104,7 +111,7 @@ class ActionsFormTest extends RulesBrowserTestBase {
 
       // Fill each given field with the value provided.
       foreach ($values as $name => $value) {
-        $this->fillField('edit-context-definitions-' . $name . '-setting', $value);
+        $this->fillField('edit-context-definitions-' . $name . '-value', $value);
       }
 
       // Check that the action can be saved.
diff --git a/tests/src/Functional/ConditionsFormTest.php b/tests/src/Functional/ConditionsFormTest.php
index b245656d..85581123 100644
--- a/tests/src/Functional/ConditionsFormTest.php
+++ b/tests/src/Functional/ConditionsFormTest.php
@@ -90,6 +90,13 @@ class ConditionsFormTest extends RulesBrowserTestBase {
     $assert->statusCodeEquals(200);
     $assert->pageTextContains('Edit ' . $condition->getLabel());
 
+    // Assert that the fields use the correct widgets, identified by class.
+    if (!empty($widgets)) {
+      foreach ($widgets as $name => $widget_id) {
+        $assert->elementExists('xpath', "//fieldset[@id='edit-context-definitions-$name' and contains(@class, 'widget-$widget_id')]");
+      }
+    }
+
     // If any field values have been specified then fill in the form and save.
     if (!empty($values)) {
 
@@ -104,7 +111,7 @@ class ConditionsFormTest extends RulesBrowserTestBase {
 
       // Fill each given field with the value provided.
       foreach ($values as $name => $value) {
-        $this->fillField('edit-context-definitions-' . $name . '-setting', $value);
+        $this->fillField('edit-context-definitions-' . $name . '-value', $value);
       }
 
       // Check that the condition can be saved.
diff --git a/tests/src/Functional/ConfigureAndExecuteTest.php b/tests/src/Functional/ConfigureAndExecuteTest.php
index 6978b9ba..7a887aa5 100644
--- a/tests/src/Functional/ConfigureAndExecuteTest.php
+++ b/tests/src/Functional/ConfigureAndExecuteTest.php
@@ -16,7 +16,7 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
    *
    * @var array
    */
-  protected static $modules = ['node', 'rules'];
+  protected static $modules = ['node', 'rules', 'typed_data'];
 
   /**
    * We use the minimal profile because we want to test local action links.
@@ -76,16 +76,16 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_data_comparison');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[data][setting]', 'node.title.0.value');
-    $this->fillField('context_definitions[value][setting]', 'Test title');
+    $this->fillField('context_definitions[data][value]', 'node.title.0.value');
+    $this->fillField('context_definitions[value][value]', 'Test title');
     $this->pressButton('Save');
 
     $this->clickLink('Add action');
     $this->fillField('Action', 'rules_system_message');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[message][setting]', 'Title matched "Test title"!');
-    $this->fillField('context_definitions[type][setting]', 'status');
+    $this->fillField('context_definitions[message][value]', 'Title matched "Test title"!');
+    $this->fillField('context_definitions[type][value]', 'status');
     $this->pressButton('Save');
 
     // One more save to permanently store the rule.
@@ -236,7 +236,7 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     $message1updated = 'RULE ONE has a new message.';
     $this->drupalGet('admin/config/workflow/rules/reactions/edit/rule1');
     $this->clickLink('Edit', 1);
-    $this->fillField('context_definitions[message][setting]', $message1updated);
+    $this->fillField('context_definitions[message][value]', $message1updated);
     // Save the action then save the rule.
     $this->pressButton('Save');
     $this->pressButton('Save');
@@ -293,15 +293,16 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_node_is_of_type');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[node][setting]', 'node');
+    $this->fillField('context_definitions[node][value]', 'node');
 
+    // Maximum allowed length of string input is 255 characters.
     $suboptimal_user_input = [
-      "  \r\nwhitespace at beginning of input\r\n",
+      "  \r\nwhitespace at beginning\r\n",
       "text\r\n",
       "trailing space  \r\n",
       "\rleading terminator\r\n",
       "  leading space\r\n",
-      "multiple words, followed by primitive values\r\n",
+      "multiple words, then primitives\r\n ",
       "0\r\n",
       "0.0\r\n",
       "128\r\n",
@@ -312,9 +313,9 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
       "two empty lines\n\r\n\r",
       "terminator n\n",
       "terminator nr\n\r",
-      "whitespace at end of input\r\n        \r\n",
+      "whitespace at end of input\r\n    \r\n",
     ];
-    $this->fillField('context_definitions[types][setting]', implode($suboptimal_user_input));
+    $this->fillField('context_definitions[types][value]', implode($suboptimal_user_input));
     $this->pressButton('Save');
 
     // One more save to permanently store the rule.
@@ -324,12 +325,12 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     // and that blank lines, leading and trailing whitespace, and wrong line
     // terminators were removed.
     $expected_config_value = [
-      "whitespace at beginning of input",
+      "whitespace at beginning",
       "text",
       "trailing space",
       "leading terminator",
       "leading space",
-      "multiple words, followed by primitive values",
+      "multiple words, then primitives",
       "0",
       "0.0",
       "128",
@@ -393,25 +394,25 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     // that the default entry field is regular text entry not a selector.
     $this->drupalGet('admin/config/workflow/rules/reactions/edit/test_rule/edit/' . $condition1->getUuid());
     $assert->buttonExists('edit-context-definitions-value-switch-button');
-    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-value-setting" and not(contains(@class, "rules-autocomplete"))]');
+    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-value-value" and not(contains(@class, "rules-autocomplete"))]');
 
     // Edit condition 2, assert that the switch button is NOT shown for node
     // and that the entry field is a selector with class rules-autocomplete.
     $this->drupalGet('admin/config/workflow/rules/reactions/edit/test_rule/edit/' . $condition2->getUuid());
     $assert->buttonNotExists('edit-context-definitions-node-switch-button');
-    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-node-setting" and contains(@class, "rules-autocomplete")]');
+    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-node-value" and contains(@class, "rules-autocomplete")]');
 
     // Edit action 1, assert that the switch button is shown for message and
     // that the default entry field is a regular text entry not a selector.
     $this->drupalGet('admin/config/workflow/rules/reactions/edit/test_rule/edit/' . $action1->getUuid());
     $assert->buttonExists('edit-context-definitions-message-switch-button');
-    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-message-setting" and not(contains(@class, "rules-autocomplete"))]');
+    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-message-value" and not(contains(@class, "rules-autocomplete"))]');
 
     // Edit action 2, assert that the switch button is NOT shown for type and
     // that the entry field is a regular text entry not a selector.
     $this->drupalGet('admin/config/workflow/rules/reactions/edit/test_rule/edit/' . $action2->getUuid());
     $assert->buttonNotExists('edit-context-definitions-type-switch-button');
-    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-type-setting" and not(contains(@class, "rules-autocomplete"))]');
+    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-type-value" and not(contains(@class, "rules-autocomplete"))]');
   }
 
 }
diff --git a/tests/src/Functional/RulesUiEmbedTest.php b/tests/src/Functional/RulesUiEmbedTest.php
index 0f3b4b9f..3d5355ba 100644
--- a/tests/src/Functional/RulesUiEmbedTest.php
+++ b/tests/src/Functional/RulesUiEmbedTest.php
@@ -31,8 +31,8 @@ class RulesUiEmbedTest extends RulesBrowserTestBase {
     $this->clickLink('Add condition');
     $this->fillField('Condition', 'rules_data_comparison');
     $this->pressButton('Continue');
-    $this->fillField('context_definitions[data][setting]', '@user.current_user_context:current_user.uid.value');
-    $this->fillField('context_definitions[value][setting]', '234');
+    $this->fillField('context_definitions[data][value]', '@user.current_user_context:current_user.uid.value');
+    $this->fillField('context_definitions[value][value]', '234');
     $this->pressButton('Save');
 
     // Now the condition should be listed. Try editing it.
@@ -40,9 +40,9 @@ class RulesUiEmbedTest extends RulesBrowserTestBase {
     $assert = $this->assertSession();
     $assert->pageTextContains('Data comparison');
     $this->clickLink('Edit');
-    $assert->fieldValueEquals('context_definitions[data][setting]', '@user.current_user_context:current_user.uid.value');
-    $assert->fieldValueEquals('context_definitions[value][setting]', '234');
-    $this->fillField('context_definitions[value][setting]', '123');
+    $assert->fieldValueEquals('context_definitions[data][value]', '@user.current_user_context:current_user.uid.value');
+    $assert->fieldValueEquals('context_definitions[value][value]', '234');
+    $this->fillField('context_definitions[value][value]', '123');
     $this->pressButton('Save');
     $assert->pageTextContains('Data comparison');
 
diff --git a/tests/src/Functional/TempStorageTest.php b/tests/src/Functional/TempStorageTest.php
index 6bdd0ce4..13f37065 100644
--- a/tests/src/Functional/TempStorageTest.php
+++ b/tests/src/Functional/TempStorageTest.php
@@ -43,7 +43,7 @@ class TempStorageTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_node_is_promoted');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[node][setting]', 'node');
+    $this->fillField('context_definitions[node][value]', 'node');
     $this->pressButton('Save');
 
     /** @var \Drupal\Tests\WebAssert $assert */
diff --git a/tests/src/Functional/UiPageTest.php b/tests/src/Functional/UiPageTest.php
index c2518671..0d8e0d8e 100644
--- a/tests/src/Functional/UiPageTest.php
+++ b/tests/src/Functional/UiPageTest.php
@@ -66,7 +66,7 @@ class UiPageTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_node_is_promoted');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[node][setting]', 'node');
+    $this->fillField('context_definitions[node][value]', 'node');
     $this->pressButton('Save');
 
     $assert->statusCodeEquals(200);
@@ -107,7 +107,7 @@ class UiPageTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_node_is_promoted');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[node][setting]', 'node');
+    $this->fillField('context_definitions[node][value]', 'node');
     $this->pressButton('Save');
 
     /** @var \Drupal\Tests\WebAssert $assert */
@@ -203,9 +203,9 @@ class UiPageTest extends RulesBrowserTestBase {
     $this->pressButton('Switch to data selection');
     $this->pressButton('Switch to the direct input mode');
 
-    $this->fillField('context_definitions[to][setting]', 'klausi@example.com');
-    $this->fillField('context_definitions[subject][setting]', 'subject');
-    $this->fillField('context_definitions[message][setting]', 'message');
+    $this->fillField('context_definitions[to][value]', 'klausi@example.com');
+    $this->fillField('context_definitions[subject][value]', 'subject');
+    $this->fillField('context_definitions[message][value]', 'message');
     $this->pressButton('Save');
 
     /** @var \Drupal\Tests\WebAssert $assert */
diff --git a/tests/src/Unit/Integration/Engine/IntegrityCheckTest.php b/tests/src/Unit/Integration/Engine/IntegrityCheckTest.php
index 89f55869..1db59925 100644
--- a/tests/src/Unit/Integration/Engine/IntegrityCheckTest.php
+++ b/tests/src/Unit/Integration/Engine/IntegrityCheckTest.php
@@ -227,8 +227,8 @@ class IntegrityCheckTest extends RulesEntityIntegrationTestBase {
       ->checkIntegrity();
     $this->assertEquals(1, iterator_count($violation_list));
     $this->assertEquals(
-      'Expected a string data type for context <em class="placeholder">Text to compare</em> but got a list data type instead.',
-      (string) $violation_list[0]->getMessage()
+      'Expected a string data type for context Text to compare but got a list data type instead.',
+      strip_tags((string) $violation_list[0]->getMessage())
     );
     $this->assertEquals($condition->getUuid(), $violation_list[0]->getUuid());
   }
@@ -252,8 +252,8 @@ class IntegrityCheckTest extends RulesEntityIntegrationTestBase {
       ->checkIntegrity();
     $this->assertEquals(1, iterator_count($violation_list));
     $this->assertEquals(
-      'Expected a list data type for context <em class="placeholder">Content types</em> but got a entity:node data type instead.',
-      (string) $violation_list[0]->getMessage()
+      'Expected a list data type for context Content types but got a entity:node data type instead.',
+      strip_tags((string) $violation_list[0]->getMessage())
     );
     $this->assertEquals($condition->getUuid(), $violation_list[0]->getUuid());
   }
@@ -277,8 +277,8 @@ class IntegrityCheckTest extends RulesEntityIntegrationTestBase {
       ->checkIntegrity();
     $this->assertEquals(1, iterator_count($violation_list));
     $this->assertEquals(
-      'Expected a entity:node data type for context <em class="placeholder">Node</em> but got a list data type instead.',
-      (string) $violation_list[0]->getMessage()
+      'Expected a entity:node data type for context Node but got a list data type instead.',
+      strip_tags((string) $violation_list[0]->getMessage())
     );
     $this->assertEquals($condition->getUuid(), $violation_list[0]->getUuid());
   }
diff --git a/tests/src/Unit/Integration/RulesAction/SystemEmailToUsersOfRoleTest.php b/tests/src/Unit/Integration/RulesAction/SystemEmailToUsersOfRoleTest.php
index 7a2bc16a..61fb5f4a 100644
--- a/tests/src/Unit/Integration/RulesAction/SystemEmailToUsersOfRoleTest.php
+++ b/tests/src/Unit/Integration/RulesAction/SystemEmailToUsersOfRoleTest.php
@@ -50,6 +50,7 @@ class SystemEmailToUsersOfRoleTest extends RulesEntityIntegrationTestBase {
   protected function setUp() {
     parent::setUp();
     $this->enableModule('user');
+    $this->enableModule('typed_data');
 
     // Mock the logger.factory service, make it return the Rules logger channel,
     // and register it in the container.
diff --git a/tests/src/Unit/Integration/RulesAction/SystemSendEmailTest.php b/tests/src/Unit/Integration/RulesAction/SystemSendEmailTest.php
index 171b2e01..f714354d 100644
--- a/tests/src/Unit/Integration/RulesAction/SystemSendEmailTest.php
+++ b/tests/src/Unit/Integration/RulesAction/SystemSendEmailTest.php
@@ -36,6 +36,8 @@ class SystemSendEmailTest extends RulesIntegrationTestBase {
    */
   protected function setUp() {
     parent::setUp();
+    $this->enableModule('typed_data');
+
     $this->mailManager = $this->prophesize(MailManagerInterface::class);
     $this->container->set('plugin.manager.mail', $this->mailManager->reveal());
 
