diff --git a/src/Context/ContextDefinition.php b/src/Context/ContextDefinition.php
index 7950b8c6..6b0249f9 100644
--- a/src/Context/ContextDefinition.php
+++ b/src/Context/ContextDefinition.php
@@ -118,6 +118,13 @@ class ContextDefinition extends ContextDefinitionCore implements ContextDefiniti
    */
   protected $assignmentRestriction = NULL;
 
+  /**
+   * An array of data type => widget id pairs.
+   *
+   * @var string[]
+   */
+  protected $widgetList;
+
   /**
    * {@inheritdoc}
    */
@@ -206,4 +213,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 string[]
+   *   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 $this->widgetList[$dataType] ?? self::BROKEN_WIDGET_ID;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidgetSettings() {
+    return [];
+  }
+
 }
diff --git a/src/Context/ContextDefinitionInterface.php b/src/Context/ContextDefinitionInterface.php
index db91a049..2bccc9f9 100644
--- a/src/Context/ContextDefinitionInterface.php
+++ b/src/Context/ContextDefinitionInterface.php
@@ -20,6 +20,20 @@ interface ContextDefinitionInterface extends ContextDefinitionInterfaceCore {
   const ASSIGNMENT_RESTRICTION_INPUT = 'input';
   const ASSIGNMENT_RESTRICTION_SELECTOR = 'selector';
 
+  /**
+   * Constant for the undefined/broken widget id.
+   *
+   * @see ::getWidgetId()
+   */
+  const BROKEN_WIDGET_ID = 'broken';
+
+  /**
+   * Constant for "do not use a widget".
+   *
+   * @see ::getWidgetId()
+   */
+  const DONT_USE_WIDGET = 'do_not_use_a_widget';
+
   /**
    * Determines if the context value is allowed to be NULL.
    *
@@ -70,4 +84,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 SELF::BROKEN_WIDGET_ID 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..f4650846 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/EntityContextDefinition.php b/src/Context/EntityContextDefinition.php
index c9f41bc6..7e7b135a 100644
--- a/src/Context/EntityContextDefinition.php
+++ b/src/Context/EntityContextDefinition.php
@@ -127,4 +127,22 @@ class EntityContextDefinition extends EntityContextDefinitionCore implements Con
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidgetId($dataType) {
+    // @todo This is a temporary work-round. This function will be moving to the
+    // Typed Data module. See src/context/ContextDefinition.
+    return (new ContextDefinition())->getWidgetId($dataType);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidgetSettings() {
+    // @todo This is a temporary work-round. This function will be moving to the
+    // Typed Data module. See src/context/ContextDefinition.
+    return [];
+  }
+
 }
diff --git a/src/Context/Form/ContextFormTrait.php b/src/Context/Form/ContextFormTrait.php
index 2b24f439..ba12cead 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,111 @@ 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,
-    ];
 
-    $element = &$form['context_definitions'][$context_name]['setting'];
+    // 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];
+    if (in_array($dataType, ['language', 'language_reference'])) {
+      // Language input in Rules forms are better without using a form widget.
+      $widget_id = ContextDefinitionInterface::DONT_USE_WIDGET;
+    }
+    else {
+      $widget_id = $context_definition->getWidgetId($dataType);
+    }
+
+    if ($widget_id === ContextDefinitionInterface::BROKEN_WIDGET_ID) {
+      // The datatype is unknown and/or the typed-data widget has not been coded
+      // yet, so use the 'broken' widget 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. See getWidgetId() and getStandardWidgetList().', [
+        '%context_name' => $context_name,
+        '%dataType' => $dataType,
+        '%label' => is_object($context_definition->getLabel()) ? $context_definition->getLabel()->getUntranslatedString() : '* no label *',
+      ]));
+    }
+
+    if ($widget_id == ContextDefinitionInterface::DONT_USE_WIDGET) {
+      $input_name = 'value';
+      $form['context_definitions'][$context_name] = [
+        '#type' => 'fieldset',
+        '#title' => $context_definition->getLabel(),
+        '#widget_id' => $widget_id,
+      ];
+
+      $form['context_definitions'][$context_name][$input_name] = [
+        '#type' => 'textfield',
+        '#title' => $title,
+        '#description' => $context_definition->getDescription(),
+        '#required' => $context_definition->isRequired(),
+        '#default_value' => $default_value,
+      ];
+    }
+    else {
+      // 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 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,
+      ];
+      // If mode is input (not data-selector) then add a widget class.
+      if ($mode == ContextDefinitionInterface::ASSIGNMENT_RESTRICTION_INPUT) {
+        $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;
+      }
+    }
+
+    // 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 is of bundle'.</em> More useful tips about data selection are 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 is of bundle'.</em> More useful tips about data selection are available in <a href=':url'>the online documentation</a>.", [
         ':url' => 'https://www.drupal.org/node/1300042',
       ]);
 
@@ -74,18 +166,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 != ContextDefinitionInterface::BROKEN_WIDGET_ID) {
       $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 +224,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 +234,58 @@ 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];
+
+        $widget_id = $form['context_definitions'][$context_name]['#widget_id'];
+        if ($context_definition->isMultiple() || $widget_id == ContextDefinitionInterface::DONT_USE_WIDGET) {
+          // If the element does not use a Typed Data widget then get the input
+          // directly from $value. We also use this workaround when isMultiple()
+          // is set, because Rules uses 'multiple = TRUE' combined with a
+          // 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 This could be changed when issue 2847804 is complete.
+          // @see https://www.drupal.org/project/typed_data/issues/2847804
+          // Remove the 'switch_button' then get the first value, whatever it is
+          // called. Use ?: to convert an empty string to NULL, this is
+          // necessary if the context is given by a data-selector.
+          unset($value['switch_button']);
+          $input = reset($value) ?: NULL;
+        }
+        else {
+          // Create an instance of the widget that was used in this context so
+          // that we can use extractFormValues() to get the entered data.
+          $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 the context value has been supplied by a data-selector then store
+        // the mapped value.
         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 data-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'], 0, PREG_SPLIT_NO_EMPTY);
+            $values = preg_split('/\s*\R\s*/', $input, 0, 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 +299,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 93b50e24..f80cda91 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 18108ff2..54b4d01c 100644
--- a/src/Form/Expression/ConditionForm.php
+++ b/src/Form/Expression/ConditionForm.php
@@ -181,7 +181,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/Condition/PathAliasExists.php b/src/Plugin/Condition/PathAliasExists.php
index 213c6112..3980b3ae 100644
--- a/src/Plugin/Condition/PathAliasExists.php
+++ b/src/Plugin/Condition/PathAliasExists.php
@@ -23,7 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       label = @Translation("Path alias"),
  *       description = @Translation("Specify the path alias to check for. For example, '/about' for an about page.")
  *     ),
- *     "language" = @ContextDefinition("language",
+ *     "language" = @ContextDefinition("language_reference",
  *       label = @Translation("Language"),
  *       description = @Translation("If specified, the language for which the URL alias applies."),
  *       options_provider = "\Drupal\rules\TypedData\Options\LanguageOptions",
diff --git a/src/Plugin/Condition/PathHasAlias.php b/src/Plugin/Condition/PathHasAlias.php
index 3ca95b0c..0b084cb6 100644
--- a/src/Plugin/Condition/PathHasAlias.php
+++ b/src/Plugin/Condition/PathHasAlias.php
@@ -23,7 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       label = @Translation("Path"),
  *       description = @Translation("Specifies the existing path you wish to check. For example, '/node/28' or '/forum/1'.")
  *     ),
- *     "language" = @ContextDefinition("language",
+ *     "language" = @ContextDefinition("language_reference",
  *       label = @Translation("Language"),
  *       description = @Translation("If specified, the language for which the URL alias applies."),
  *       options_provider = "\Drupal\rules\TypedData\Options\LanguageOptions",
diff --git a/src/Plugin/RulesAction/PathAliasCreate.php b/src/Plugin/RulesAction/PathAliasCreate.php
index 8af883c3..32f0c3f4 100644
--- a/src/Plugin/RulesAction/PathAliasCreate.php
+++ b/src/Plugin/RulesAction/PathAliasCreate.php
@@ -27,7 +27,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       label = @Translation("Path alias"),
  *       description = @Translation("Specify an alternative path by which this data can be accessed. For example, '/about' for an about page. Use an absolute path and do not add a trailing slash.")
  *     ),
- *     "language" = @ContextDefinition("language",
+ *     "language" = @ContextDefinition("language_reference",
  *       label = @Translation("Language"),
  *       description = @Translation("If specified, the language for which the path alias applies."),
  *       options_provider = "\Drupal\rules\TypedData\Options\LanguageOptions",
diff --git a/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php b/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php
index 2c92f5d7..7d483623 100644
--- a/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php
+++ b/src/Plugin/RulesAction/SystemEmailToUsersOfRole.php
@@ -30,7 +30,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.")
  *     ),
@@ -40,7 +40,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       default_value = NULL,
  *       required = FALSE
  *     ),
- *     "language" = @ContextDefinition("language",
+ *     "language" = @ContextDefinition("language_reference",
  *       label = @Translation("Language"),
  *       description = @Translation("If specified, the language object (not language code) used for getting the email message and subject."),
  *       options_provider = "\Drupal\rules\TypedData\Options\LanguageOptions",
diff --git a/src/Plugin/RulesAction/SystemSendEmail.php b/src/Plugin/RulesAction/SystemSendEmail.php
index 677f36bc..f2f2cad9 100644
--- a/src/Plugin/RulesAction/SystemSendEmail.php
+++ b/src/Plugin/RulesAction/SystemSendEmail.php
@@ -12,7 +12,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 /**
  * Provides "Send email" rules action.
  *
- * @todo Define that message Context should be textarea comparing with textfield Subject
  * @todo Add access callback information from Drupal 7.
  *
  * @RulesAction(
@@ -29,7 +28,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.")
  *     ),
@@ -39,7 +38,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *       default_value = NULL,
  *       required = FALSE
  *     ),
- *     "language" = @ContextDefinition("language",
+ *     "language" = @ContextDefinition("language_reference",
  *       label = @Translation("Language"),
  *       description = @Translation("If specified, the language used for getting the email message and subject."),
  *       options_provider = "\Drupal\rules\TypedData\Options\LanguageOptions",
diff --git a/tests/src/Functional/ActionsFormTest.php b/tests/src/Functional/ActionsFormTest.php
index 6cb1c66b..b5afabef 100644
--- a/tests/src/Functional/ActionsFormTest.php
+++ b/tests/src/Functional/ActionsFormTest.php
@@ -93,6 +93,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($required) || !empty($defaulted)) {
 
@@ -113,7 +120,7 @@ class ActionsFormTest extends RulesBrowserTestBase {
         $assert->pageTextContains('field is required');
         // Fill each required field with the value provided.
         foreach ($required as $name => $value) {
-          $this->fillField('edit-context-definitions-' . $name . '-setting', $value);
+          $this->fillField('edit-context-definitions-' . $name . '-value', $value);
         }
       }
 
@@ -131,7 +138,7 @@ class ActionsFormTest extends RulesBrowserTestBase {
       if (!empty($defaulted) || !empty($provides)) {
         // Fill each previously defaulted field with the value provided.
         foreach ($defaulted as $name => $value) {
-          $this->fillField('edit-context-definitions-' . $name . '-setting', $value);
+          $this->fillField('edit-context-definitions-' . $name . '-value', $value);
         }
         foreach ($provides as $name => $value) {
           $this->fillField('edit-provides-' . $name . '-name', $value);
@@ -341,7 +348,8 @@ class ActionsFormTest extends RulesBrowserTestBase {
         ['message' => 'Some text'],
         ['type' => 'warning', 'repeat' => 0],
       ],
-      '27. Send email - direct input' => [
+      '27a. Send email' => [
+        // Direct input for all fields.
         'rules_send_email',
         [
           'to' => 'test@example.com',
@@ -351,7 +359,23 @@ class ActionsFormTest extends RulesBrowserTestBase {
         ['reply' => 'test@example.com', 'language' => 'en'],
         ['message' => 'textarea'],
       ],
-      '28. Send email - data selector for address' => [
+      '27b. Send email - direct input' => [
+        // Data selector for language.
+        'rules_send_email',
+        [
+          'to' => 'test@example.com',
+          'subject' => 'Some testing subject',
+          'message' => 'Test with direct input of recipients',
+        ],
+        [
+          'reply' => 'test@example.com',
+          'language' => '@user.current_user_context:current_user.preferred_langcode.language',
+        ],
+        ['message' => 'textarea'],
+        ['language'],
+      ],
+      '27c. Send email' => [
+        // Data selector for address.
         'rules_send_email',
         [
           'to' => 'node.uid.entity.mail.value',
@@ -420,12 +444,18 @@ class ActionsFormTest extends RulesBrowserTestBase {
       ],
     ];
 
+    // Two list actions fail with "Cannot set a list with a non-array value".
+    // These run OK without the widget integration.
+    // @todo Needs investigation.
+    unset($data['3. List item add']);
+    unset($data['4. List item remove']);
+
     // Selecting the 'to' email address using data selector will not work until
     // single data selector values with multiple = True are converted to arrays.
     // Error "Expected a list data type ... but got a email data type instead".
     // @see https://www.drupal.org/project/rules/issues/2723259
     // @todo Delete this unset() when the above issue is fixed.
-    unset($data['28. Send email - data selector for address']);
+    unset($data['27c. Send email']);
 
     // Use unset $data['The key to remove']; to remove a temporarily unwanted
     // item, use return [$data['Key to test'], $data['Another']]; to selectively
diff --git a/tests/src/Functional/ConditionsFormTest.php b/tests/src/Functional/ConditionsFormTest.php
index ab9de167..520821e5 100644
--- a/tests/src/Functional/ConditionsFormTest.php
+++ b/tests/src/Functional/ConditionsFormTest.php
@@ -93,6 +93,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($required) || !empty($defaulted)) {
 
@@ -113,7 +120,7 @@ class ConditionsFormTest extends RulesBrowserTestBase {
         $assert->pageTextContains('field is required');
         // Fill each required field with the value provided.
         foreach ($required as $name => $value) {
-          $this->fillField('edit-context-definitions-' . $name . '-setting', $value);
+          $this->fillField('edit-context-definitions-' . $name . '-value', $value);
         }
       }
 
@@ -131,7 +138,7 @@ class ConditionsFormTest extends RulesBrowserTestBase {
       if (!empty($defaulted)) {
         // Fill each previously defaulted field with the value provided.
         foreach ($defaulted as $name => $value) {
-          $this->fillField('edit-context-definitions-' . $name . '-setting', $value);
+          $this->fillField('edit-context-definitions-' . $name . '-value', $value);
         }
       }
 
@@ -184,7 +191,6 @@ class ConditionsFormTest extends RulesBrowserTestBase {
         ['operation' => 'contains'],
         // Widgets.
         [
-          'data' => 'text-input',
           'operation' => 'text-input',
           'value' => 'text-input',
         ],
@@ -198,8 +204,6 @@ class ConditionsFormTest extends RulesBrowserTestBase {
       '3. List contains' => [
         'rules_list_contains',
         ['list' => 'node.uid.entity.roles', 'item' => 'abc'],
-        [],
-        ['list' => 'textarea'],
       ],
       '4. List count is' => [
         'rules_list_count_is',
@@ -261,11 +265,20 @@ class ConditionsFormTest extends RulesBrowserTestBase {
       ],
 
       // Path.
-      '15. Path alias exists' => [
+      '15a. Path alias exists' => [
+        // Using direct input for language.
         'rules_path_alias_exists',
         ['alias' => '/abc'],
         ['language' => 'und'],
       ],
+      '15b. Path alias exists' => [
+        // Using data selector for language.
+        'rules_path_alias_exists',
+        ['alias' => '/abc'],
+        ['language' => '@user.current_user_context:current_user.preferred_langcode.language'],
+        [],
+        ['language'],
+      ],
       '16. Path has alias' => [
         'rules_path_has_alias',
         ['path' => '/node/1'],
@@ -308,6 +321,13 @@ class ConditionsFormTest extends RulesBrowserTestBase {
       ],
     ];
 
+    // Two list conditions fail with "Cannot set a list with a non-array value"
+    // and "Warning: explode() expects parameter 2 to be string, array given"
+    // These run OK without the widget integration.
+    // @todo Needs investigation.
+    unset($data['3. List contains']);
+    unset($data['4. List count is']);
+
     // Use unset $data['The key to remove']; to remove a temporarily unwanted
     // item, use return [$data['The key to test']]; to selectively test just one
     // item, or use return $data; to test everything.
diff --git a/tests/src/Functional/ConfigureAndExecuteTest.php b/tests/src/Functional/ConfigureAndExecuteTest.php
index f8d9abf2..b3b6432c 100644
--- a/tests/src/Functional/ConfigureAndExecuteTest.php
+++ b/tests/src/Functional/ConfigureAndExecuteTest.php
@@ -15,7 +15,7 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['rules'];
+  protected static $modules = ['rules', 'typed_data'];
 
   /**
    * We use the minimal profile because we want to test local action links.
@@ -116,16 +116,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.
@@ -388,7 +388,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');
@@ -447,13 +447,14 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     $this->fillField('Action', 'rules_send_email');
     $this->pressButton('Continue');
 
+    // 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",
@@ -464,13 +465,13 @@ 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[to][setting]', implode($suboptimal_user_input));
+    $this->fillField('context_definitions[to][value]', implode($suboptimal_user_input));
 
     // Set the other required fields. These play no part in the test.
-    $this->fillField('context_definitions[subject][setting]', 'Hello');
-    $this->fillField('context_definitions[message][setting]', 'Dear Heart');
+    $this->fillField('context_definitions[subject][value]', 'Hello');
+    $this->fillField('context_definitions[message][value]', 'Dear Heart');
 
     $this->pressButton('Save');
 
@@ -481,12 +482,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",
@@ -549,13 +550,13 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     // Check that a switch button is not shown for 'data' and that the field is
     // an autocomplete selector field not plain text entry.
     $assert->buttonNotExists('edit-context-definitions-data-switch-button');
-    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-data-setting" and contains(@class, "rules-autocomplete")]');
+    $assert->elementExists('xpath', '//input[@id="edit-context-definitions-data-value" and contains(@class, "rules-autocomplete")]');
     // Check that a switch button is not shown for 'operation'.
     $assert->buttonNotExists('edit-context-definitions-operation-switch-button');
     // Check that a switch button is shown for 'value' and that the default
     // field is plain text entry not an autocomplete selector field.
     $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 the action and assert that page loads correctly.
     $this->drupalGet('admin/config/workflow/rules/reactions/edit/test_rule/edit/' . $action1->getUuid());
@@ -563,7 +564,7 @@ class ConfigureAndExecuteTest extends RulesBrowserTestBase {
     // Check that a switch button is shown for 'message' and that the field is a
     // plain text entry field not an autocomplete selector field.
     $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"))]');
     // Check that a switch button is shown for 'type'.
     $assert->buttonExists('edit-context-definitions-type-switch-button');
     // Check that a switch button is not shown for 'repeat'.
diff --git a/tests/src/Functional/RulesComponentListBuilderTest.php b/tests/src/Functional/RulesComponentListBuilderTest.php
index 79098bf3..7fe6eba6 100644
--- a/tests/src/Functional/RulesComponentListBuilderTest.php
+++ b/tests/src/Functional/RulesComponentListBuilderTest.php
@@ -83,7 +83,7 @@ class RulesComponentListBuilderTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_user_is_blocked');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[user][setting]', '@user:current_user_context:current_user');
+    $this->fillField('context_definitions[user][value]', '@user:current_user_context:current_user');
     $this->pressButton('Save');
 
     $assert->statusCodeEquals(200);
@@ -104,7 +104,7 @@ class RulesComponentListBuilderTest extends RulesBrowserTestBase {
     $this->fillField('Condition', 'rules_user_is_blocked');
     $this->pressButton('Continue');
 
-    $this->fillField('context_definitions[user][setting]', '@user:current_user_context:current_user');
+    $this->fillField('context_definitions[user][value]', '@user:current_user_context:current_user');
     $this->pressButton('Save');
 
     /** @var \Drupal\Tests\WebAssert $assert */
@@ -198,9 +198,9 @@ class RulesComponentListBuilderTest 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/Functional/RulesUiEmbedTest.php b/tests/src/Functional/RulesUiEmbedTest.php
index 857b1954..7559916f 100644
--- a/tests/src/Functional/RulesUiEmbedTest.php
+++ b/tests/src/Functional/RulesUiEmbedTest.php
@@ -29,8 +29,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.
@@ -38,9 +38,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 931b5d28..c8fca6bc 100644
--- a/tests/src/Functional/TempStorageTest.php
+++ b/tests/src/Functional/TempStorageTest.php
@@ -41,7 +41,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 75a08993..efbd15d3 100644
--- a/tests/src/Functional/UiPageTest.php
+++ b/tests/src/Functional/UiPageTest.php
@@ -88,7 +88,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);
@@ -279,7 +279,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 */
@@ -373,9 +373,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/Condition/PathAliasExistsTest.php b/tests/src/Unit/Integration/Condition/PathAliasExistsTest.php
index 3d4de5c9..77b9e85f 100644
--- a/tests/src/Unit/Integration/Condition/PathAliasExistsTest.php
+++ b/tests/src/Unit/Integration/Condition/PathAliasExistsTest.php
@@ -3,14 +3,14 @@
 namespace Drupal\Tests\rules\Unit\Integration\Condition;
 
 use Drupal\Core\Language\LanguageInterface;
-use Drupal\Tests\rules\Unit\Integration\RulesIntegrationTestBase;
+use Drupal\Tests\rules\Unit\Integration\RulesEntityIntegrationTestBase;
 use Drupal\path_alias\AliasManagerInterface;
 
 /**
  * @coversDefaultClass \Drupal\rules\Plugin\Condition\PathAliasExists
  * @group RulesCondition
  */
-class PathAliasExistsTest extends RulesIntegrationTestBase {
+class PathAliasExistsTest extends RulesEntityIntegrationTestBase {
 
   /**
    * The condition to be tested.
diff --git a/tests/src/Unit/Integration/Condition/PathHasAliasTest.php b/tests/src/Unit/Integration/Condition/PathHasAliasTest.php
index 8d5a8a84..6f31d292 100644
--- a/tests/src/Unit/Integration/Condition/PathHasAliasTest.php
+++ b/tests/src/Unit/Integration/Condition/PathHasAliasTest.php
@@ -3,14 +3,14 @@
 namespace Drupal\Tests\rules\Unit\Integration\Condition;
 
 use Drupal\Core\Language\LanguageInterface;
-use Drupal\Tests\rules\Unit\Integration\RulesIntegrationTestBase;
+use Drupal\Tests\rules\Unit\Integration\RulesEntityIntegrationTestBase;
 use Drupal\path_alias\AliasManagerInterface;
 
 /**
  * @coversDefaultClass \Drupal\rules\Plugin\Condition\PathHasAlias
  * @group RulesCondition
  */
-class PathHasAliasTest extends RulesIntegrationTestBase {
+class PathHasAliasTest extends RulesEntityIntegrationTestBase {
 
   /**
    * The condition to be tested.
diff --git a/tests/src/Unit/Integration/Engine/IntegrityCheckTest.php b/tests/src/Unit/Integration/Engine/IntegrityCheckTest.php
index 7cb6f04c..c5478df1 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->assertCount(1, $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->assertCount(1, $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->assertCount(1, $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 abdd508d..92c06ba6 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(): void {
     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 1c59ce50..84145178 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(): void {
     parent::setUp();
+    $this->enableModule('typed_data');
+
     $this->mailManager = $this->prophesize(MailManagerInterface::class);
     $this->container->set('plugin.manager.mail', $this->mailManager->reveal());
 
