diff --git a/composer.json b/composer.json
index 6c97470805a9f510515b34904fef1177d817ae36..d04004f4c6fba873fe9eb7ee54a2280c55e746ea 100644
--- a/composer.json
+++ b/composer.json
@@ -183,7 +183,7 @@
         "drupal/views_ajax_history": "1.5",
         "drupal/views_autocomplete_filters": "1.3",
         "drupal/views_bootstrap": "3.1",
-        "drupal/views_bulk_operations": "3.9",
+        "drupal/views_bulk_operations": "3.10",
         "drupal/views_fieldsets": "3.3",
         "drupal/views_infinite_scroll": "1.7",
         "drupal/views_slideshow": "4.4",
diff --git a/composer.lock b/composer.lock
index 3cfc95e1442f4d01d0d312a43b43aba26138161d..6d5b3ce12dbc7c9fc937138ecc2b9cfbc786912f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "483958ad644d5bd3351af88c32212e2b",
+    "content-hash": "3e27a76fd706d895eb8803af3caafa29",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -8473,17 +8473,17 @@
         },
         {
             "name": "drupal/views_bulk_operations",
-            "version": "3.9.0",
+            "version": "3.10.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/views_bulk_operations.git",
-                "reference": "8.x-3.9"
+                "reference": "8.x-3.10"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.9.zip",
-                "reference": "8.x-3.9",
-                "shasum": "d7f6e50c31d21ff32f21e8f4aaedb52f6dee2da8"
+                "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.10.zip",
+                "reference": "8.x-3.10",
+                "shasum": "e346c2a72fc9a1ae8af418e6a02076f52c0fcc7b"
             },
             "require": {
                 "drupal/core": "^8.8 || ^9"
@@ -8497,8 +8497,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-3.9",
-                    "datestamp": "1597319021",
+                    "version": "8.x-3.10",
+                    "datestamp": "1608795018",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index e0aade52f2bc82bf9887eaf6622d5bef176e2262..3045459dbe7e86543c4e02dfb0f572c05bec9149 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -8738,18 +8738,18 @@
     },
     {
         "name": "drupal/views_bulk_operations",
-        "version": "3.9.0",
-        "version_normalized": "3.9.0.0",
+        "version": "3.10.0",
+        "version_normalized": "3.10.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/views_bulk_operations.git",
-            "reference": "8.x-3.9"
+            "reference": "8.x-3.10"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.9.zip",
-            "reference": "8.x-3.9",
-            "shasum": "d7f6e50c31d21ff32f21e8f4aaedb52f6dee2da8"
+            "url": "https://ftp.drupal.org/files/projects/views_bulk_operations-8.x-3.10.zip",
+            "reference": "8.x-3.10",
+            "shasum": "e346c2a72fc9a1ae8af418e6a02076f52c0fcc7b"
         },
         "require": {
             "drupal/core": "^8.8 || ^9"
@@ -8763,8 +8763,8 @@
         "type": "drupal-module",
         "extra": {
             "drupal": {
-                "version": "8.x-3.9",
-                "datestamp": "1597319021",
+                "version": "8.x-3.10",
+                "datestamp": "1608795018",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
diff --git a/web/modules/views_bulk_operations/js/frontUi.js b/web/modules/views_bulk_operations/js/frontUi.js
index 97bef561112012e4926c74117c4e73bd5f617ef4..82013da155ed4deef854abd508b30ae0153846f8 100644
--- a/web/modules/views_bulk_operations/js/frontUi.js
+++ b/web/modules/views_bulk_operations/js/frontUi.js
@@ -38,14 +38,14 @@
           if (event.which === 13) {
             event.preventDefault();
             event.stopPropagation();
-            selectionObject.update(this.checked, index, $(this).val());
+            selectionObject.update(!this.checked, index, $(this).val());
             $(this).trigger('click');
           }
           if (event.which === 32) {
-            selectionObject.update(this.checked, index, $(this).val());
+            selectionObject.update(!this.checked, index, $(this).val());
           }
         });
-        $element.on('mousedown', function (event) {
+        $element.on('click', function (event) {
           // Act only on left button click.
           if (event.which === 1) {
             selectionObject.update(this.checked, index, $(this).val());
@@ -70,8 +70,8 @@
 
         var list = {}, op = '';
         if (index === 'selection_method_change') {
-          var op = state ? 'method_include' : 'method_exclude';
-          if (!state) {
+          var op = state ? 'method_exclude' : 'method_include';
+          if (state) {
             list = this.list[index];
           }
         }
@@ -82,7 +82,7 @@
           else {
             list = this.list[index];
           }
-          op = state ? 'remove' : 'add';
+          op = state ? 'add' : 'remove';
         }
 
         var $placeholder = this.$placeholder;
@@ -167,6 +167,10 @@
           this.checked = value;
         });
 
+        // Clear the selection information if exists.
+        $vboForm.find('.vbo-info-list-wrapper').each(function () {
+          $(this).html('');
+        });
       });
 
       if ($multiSelectElement.length) {
diff --git a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml
index cbe4230c851dbbe3d0837f8658310751ef1ee623..3b5675c624840a62212724f42cff8e6be452c8b1 100644
--- a/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml
+++ b/web/modules/views_bulk_operations/modules/actions_permissions/actions_permissions.info.yml
@@ -6,7 +6,7 @@ core_version_requirement: ^8 || ^9
 dependencies:
   - drupal:views_bulk_operations
 
-# Information added by Drupal.org packaging script on 2020-08-13
-version: '8.x-3.9'
+# Information added by Drupal.org packaging script on 2020-12-24
+version: '8.x-3.10'
 project: 'views_bulk_operations'
-datestamp: 1597319023
+datestamp: 1608795021
diff --git a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php
index 6d9e813d7d14af30026c318010150fdc9765466b..3c7b9d23cc080f6a9f0160eeb8b01256e4863171 100644
--- a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php
+++ b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/src/Plugin/Action/ViewsBulkOperationExampleAction.php
@@ -83,7 +83,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta
    * Submit handler for the action configuration form.
    *
    * If not implemented, the cleaned form values will be
-   * passed direclty to the action $configuration parameter.
+   * passed directly to the action $configuration parameter.
    *
    * @param array $form
    *   Form array.
diff --git a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml
index f221f4c2ca8ff269643bd574d0424bb770885b3c..2d2965100fc775258329b7ff2eae5911bf5a18de 100644
--- a/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml
+++ b/web/modules/views_bulk_operations/modules/views_bulk_operations_example/views_bulk_operations_example.info.yml
@@ -6,7 +6,7 @@ core_version_requirement: ^8 || ^9
 dependencies:
   - drupal:views_bulk_operations
 
-# Information added by Drupal.org packaging script on 2020-08-13
-version: '8.x-3.9'
+# Information added by Drupal.org packaging script on 2020-12-24
+version: '8.x-3.10'
 project: 'views_bulk_operations'
-datestamp: 1597319023
+datestamp: 1608795021
diff --git a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php
index b03c244932e6b983a7a239d2f0a4f3163bc0f318..643ccdce938d0e85a69ad76d625d3b81173c8d2a 100644
--- a/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php
+++ b/web/modules/views_bulk_operations/src/Commands/ViewsBulkOperationsCommands.php
@@ -76,7 +76,7 @@ public function __construct(
    * @option display-id
    *   ID of the display to use.
    * @option args
-   *   View arguments (slash is a delimeter).
+   *   View arguments (slash is a delimiter).
    * @option exposed
    *   Exposed filters (query string format).
    * @option batch-size
diff --git a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php
index e4bce65029384a033a64e118a9b95c46131a8747..a5d4972b2f3def700dd5b6dd98de650413e445d7 100644
--- a/web/modules/views_bulk_operations/src/Form/ConfigureAction.php
+++ b/web/modules/views_bulk_operations/src/Form/ConfigureAction.php
@@ -92,7 +92,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $view_id
     $form['list'] = $this->getListRenderable($form_data);
 
     // :D Make sure the submit button is at the bottom of the form
-    // and is editale from the action buildConfigurationForm method.
+    // and is editable from the action buildConfigurationForm method.
     $form['actions']['#weight'] = 666;
     $form['actions']['submit'] = [
       '#type' => 'submit',
diff --git a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php
index 9d83f90c122992c8d68dda3d6420daf48b8e4dab..5fe4260896908a5cfb0f4c21daac2267df09ae2a 100644
--- a/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php
+++ b/web/modules/views_bulk_operations/src/Form/ViewsBulkOperationsFormTrait.php
@@ -108,6 +108,8 @@ protected function getListRenderable(array $form_data) {
       $renderable['#title'] = $this->t('Selected @count entities:', ['@count' => $form_data['selected_count']]);
     }
 
+    $renderable['#wrapper_attributes'] = ['class' => ['vbo-info-list-wrapper']];
+
     return $renderable;
   }
 
diff --git a/web/modules/views_bulk_operations/src/Plugin/Action/EntityDeleteAction.php b/web/modules/views_bulk_operations/src/Plugin/Action/EntityDeleteAction.php
index 9243b259c3419df15e67a649d1e4626545d8fa8a..0c1c08cef0afcc2a1730834c2b26acd9cfc6c10a 100644
--- a/web/modules/views_bulk_operations/src/Plugin/Action/EntityDeleteAction.php
+++ b/web/modules/views_bulk_operations/src/Plugin/Action/EntityDeleteAction.php
@@ -3,6 +3,7 @@
 namespace Drupal\views_bulk_operations\Plugin\Action;
 
 use Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionBase;
+use Drupal\Core\Entity\TranslatableInterface;
 use Drupal\Core\Session\AccountInterface;
 
 /**
@@ -10,7 +11,7 @@
  *
  * @Action(
  *   id = "views_bulk_operations_delete_entity",
- *   label = @Translation("Delete selected entities"),
+ *   label = @Translation("Delete selected entities / translations"),
  *   type = "",
  *   confirm = TRUE,
  * )
@@ -21,8 +22,16 @@ class EntityDeleteAction extends ViewsBulkOperationsActionBase {
    * {@inheritdoc}
    */
   public function execute($entity = NULL) {
-    $entity->delete();
-    return $this->t('Delete entities');
+    if ($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) {
+      $untranslated_entity = $entity->getUntranslated();
+      $untranslated_entity->removeTranslation($entity->language()->getId());
+      $untranslated_entity->save();
+      return $this->t('Delete translations');
+    }
+    else {
+      $entity->delete();
+      return $this->t('Delete entities');
+    }
   }
 
   /**
diff --git a/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php b/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php
index 8579de308f6f74c5a8a24262c8899417c25cb771..1f1f00d7bd68f8b880d478ee7c366727e6ae79c0 100644
--- a/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php
+++ b/web/modules/views_bulk_operations/src/Plugin/views/field/ViewsBulkOperationsBulkForm.php
@@ -202,8 +202,11 @@ public function init(ViewExecutable $view, DisplayPluginBase $display, array &$o
    * This function must be called a bit later, when the view
    * query has been built. Also, no point doing this on the view
    * admin page.
+   *
+   * @param array $bulk_form_keys
+   *   The calculated bulk form keys.
    */
-  protected function updateTempstoreData() {
+  protected function updateTempstoreData(array $bulk_form_keys = NULL) {
     // Initialize tempstore object and get data if available.
     $this->tempStoreData = $this->getTempstoreData($this->view->id(), $this->view->current_display);
 
@@ -216,6 +219,11 @@ protected function updateTempstoreData() {
       'exposed_input' => $this->view->getExposedInput(),
     ];
 
+    // Add bulk form keys when the form is displayed.
+    if (isset($bulk_form_keys)) {
+      $variable['bulk_form_keys'] = $bulk_form_keys;
+    }
+
     // A fix to account for empty initial exposed input vs the default values
     // difference.
     if (empty($variable['exposed_input'])) {
@@ -580,8 +588,29 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
     $action_options = $this->getBulkOptions();
     if (!empty($this->view->result) && !empty($action_options)) {
 
-      // Update tempstore data.
-      $this->updateTempstoreData();
+      // Calculate bulk form keys for all rows.
+      $bulk_form_keys = [];
+      $base_field = $this->view->storage->get('base_field');
+      foreach ($this->view->result as $row_index => $row) {
+        $entity = $this->getEntity($row);
+        $bulk_form_keys[$row_index] = self::calculateEntityBulkFormKey(
+          $entity,
+          $row->{$base_field}
+        );
+      }
+
+      // Update and fetch tempstore data to be available from this point
+      // as it's needed for proper functioning of further logic.
+      // Update tempstore data with bulk form keys only when the form is
+      // displayed, but not when the form is being built before submission
+      // (data is subject to change - new entities added or deleted after
+      // the form display). TODO: consider using $form_state->set() instead.
+      if (empty($form_state->getUserInput())) {
+        $this->updateTempstoreData($bulk_form_keys);
+      }
+      else {
+        $this->updateTempstoreData();
+      }
 
       $form[$this->options['id']]['#tree'] = TRUE;
 
@@ -595,14 +624,8 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
 
       // Render checkboxes for all rows.
       $page_selected = [];
-      $base_field = $this->view->storage->get('base_field');
       foreach ($this->view->result as $row_index => $row) {
-        $entity = $this->getEntity($row);
-        $bulk_form_key = self::calculateEntityBulkFormKey(
-          $entity,
-          $row->{$base_field}
-        );
-
+        $bulk_form_key = $bulk_form_keys[$row_index];
         $checked = isset($this->tempStoreData['list'][$bulk_form_key]);
         if (!empty($this->tempStoreData['exclude_mode'])) {
           $checked = !$checked;
@@ -618,6 +641,15 @@ public function viewsForm(array &$form, FormStateInterface $form_state) {
           '#default_value' => $checked,
           '#return_value' => $bulk_form_key,
         ];
+
+        // We should use #value instead of #default_value to always apply
+        // the plugin's own saved checkbox state (data being changed after form
+        // submission results in wrong values applied by the FAPI),
+        // however - automated tests fail if it's done this way.
+        // We have to apply values conditionally for tests to pass.
+        if (isset($element['#value']) && $element['#value'] != $checked) {
+          $element['#value'] = $checked;
+        }
       }
 
       // Ensure a consistent container for filters/operations
@@ -847,26 +879,22 @@ public function viewsFormSubmit(array &$form, FormStateInterface $form_state) {
       }
 
       // Update list data with the current page selection.
-      if ($form_state->getValue('select_all')) {
-        foreach ($form_state->getValue($this->options['id']) as $row_index => $bulkFormKey) {
-          if ($bulkFormKey) {
-            unset($this->tempStoreData['list'][$bulkFormKey]);
-          }
-          else {
-            $row_bulk_form_key = $form[$this->options['id']][$row_index]['#return_value'];
-            $this->tempStoreData['list'][$row_bulk_form_key] = $this->getListItem($row_bulk_form_key);
-          }
-        }
+      $selected_keys = [];
+      $input = $form_state->getUserInput();
+      foreach ($input[$this->options['id']] as $row_index => $bulk_form_key) {
+        $selected_keys[$bulk_form_key] = $bulk_form_key;
       }
-      else {
-        foreach ($form_state->getValue($this->options['id']) as $row_index => $bulkFormKey) {
-          if ($bulkFormKey) {
-            $this->tempStoreData['list'][$bulkFormKey] = $this->getListItem($bulkFormKey);
-          }
-          else {
-            $row_bulk_form_key = $form[$this->options['id']][$row_index]['#return_value'];
-            unset($this->tempStoreData['list'][$row_bulk_form_key]);
-          }
+      $select_all = $form_state->getValue('select_all');
+
+      foreach ($this->tempStoreData['bulk_form_keys'] as $bulk_form_key) {
+        if (
+          (isset($selected_keys[$bulk_form_key]) && !$select_all) ||
+          (!isset($selected_keys[$bulk_form_key]) && $select_all)
+        ) {
+          $this->tempStoreData['list'][$bulk_form_key] = $this->getListItem($bulk_form_key);
+        }
+        else {
+          unset($this->tempStoreData['list'][$bulk_form_key]);
         }
       }
 
@@ -971,6 +999,18 @@ public function viewsFormValidate(&$form, FormStateInterface $form_state) {
         $actionObject->validateConfigurationForm($form['header'][$this->options['id']]['configuration'], $form_state);
       }
     }
+
+    // Update bulk form key list if the form has errors, as data might have
+    // changed before validation took place.
+    if ($form_state->getErrors()) {
+      $bulk_form_keys = [];
+      foreach ($form[$this->options['id']] as $row_index => $element) {
+        if (is_numeric($row_index) && isset($element['#return_value'])) {
+          $bulk_form_keys[$row_index] = $element['#return_value'];
+        }
+      }
+      $this->updateTempstoreData($bulk_form_keys);
+    }
   }
 
   /**
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php
index 40af081bb9af06ef4ca7d1137af36110e22ddcc6..00db7e5153e28cc0165d5dd0fa85ec6bb5783566 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionManager.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Symfony\Component\EventDispatcher\Event;
 use Drupal\Component\Plugin\Exception\PluginNotFoundException;
 
@@ -26,6 +27,13 @@ class ViewsBulkOperationsActionManager extends ActionManager {
    */
   protected $eventDispatcher;
 
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
   /**
    * Additional parameters passed to alter event.
    *
@@ -45,15 +53,21 @@ class ViewsBulkOperationsActionManager extends ActionManager {
    *   The module handler to invoke the alter hook with.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
    *   The event dispatcher service.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   Entity type manager.
    */
   public function __construct(
     \Traversable $namespaces,
     CacheBackendInterface $cacheBackend,
     ModuleHandlerInterface $moduleHandler,
-    EventDispatcherInterface $eventDispatcher
+    EventDispatcherInterface $eventDispatcher,
+    EntityTypeManagerInterface $entityTypeManager
   ) {
     parent::__construct($namespaces, $cacheBackend, $moduleHandler);
+
     $this->eventDispatcher = $eventDispatcher;
+    $this->entityTypeManager = $entityTypeManager;
+
     $this->setCacheBackend($cacheBackend, 'views_bulk_operations_action_info');
   }
 
@@ -63,18 +77,28 @@ public function __construct(
   protected function findDefinitions() {
     $definitions = $this->getDiscovery()->getDefinitions();
 
-    // Incompatible actions.
-    $incompatible = [
-      // Deprecated anyway, to be deleted eventually.
-      'node_delete_action',
-      // Those are up to date.
-      'entity:delete_action:node',
-      'user_cancel_user_action',
-    ];
-
+    $entity_type_definitions = $this->entityTypeManager->getDefinitions();
     foreach ($definitions as $plugin_id => &$definition) {
       $this->processDefinition($definition, $plugin_id);
-      if (empty($definition) || in_array($definition['id'], $incompatible)) {
+
+      // We only allow actions of existing entity type and empty
+      // type meaning it's applicable to all entity types.
+      if (
+        empty($definition) ||
+        (
+          !empty($definition['type']) &&
+          !isset($entity_type_definitions[$definition['type']])
+        )
+      ) {
+        unset($definitions[$plugin_id]);
+      }
+
+      // Filter definitions that are incompatible due to applied core
+      // configuration form workaround (using confirm_form_route for config
+      // forms and using action execute() method for purposes other than
+      // actual action execution). Luckily, core also has useful actions
+      // without the workaround, like node_assign_owner_action.
+      if (!in_array('Drupal\views_bulk_operations\Action\ViewsBulkOperationsActionInterface', class_implements($definition['class'])) && (!empty($definition['confirm_form_route_name']))) {
         unset($definitions[$plugin_id]);
       }
     }
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php
index 801f647e15b78ed6fd747e8abc89510ee482d2c6..c40294a84a2bb3793607f56732ed42b5a775cdbb 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsActionProcessor.php
@@ -127,7 +127,7 @@ public function initialize(array $view_data, $view = NULL) {
       $this->queue = [];
     }
 
-    $this->excludeMode = $view_data['exclude_mode'];
+    $this->excludeMode = !empty($view_data['exclude_mode']);
 
     if (isset($view_data['action_id'])) {
       if (!isset($view_data['configuration'])) {
@@ -210,7 +210,7 @@ public function getPageList($page) {
       $this->view->setExposedInput(['_views_bulk_operations_override' => TRUE]);
     }
 
-    // In some cases we may encounter nondeterministic bahaviour in
+    // In some cases we may encounter nondeterministic behaviour in
     // db queries with sorts allowing different order of results.
     // To fix this we're removing all sorts and setting one sorting
     // rule by the view base id field.
@@ -303,7 +303,7 @@ public function populateQueue(array $data, array &$context = []) {
       $batch_list = $list;
     }
 
-    $this->view->setItemsPerPage(0);
+    $this->view->setItemsPerPage($batch_size);
     $this->view->setCurrentPage(0);
     $this->view->setOffset(0);
     $this->view->initHandlers();
diff --git a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php
index d72ae49e0eddec0be7e605e254418708d527c015..a3466f4b340387cc65ca05074d98751e6b06cee2 100644
--- a/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php
+++ b/web/modules/views_bulk_operations/src/Service/ViewsBulkOperationsViewData.php
@@ -38,7 +38,7 @@ class ViewsBulkOperationsViewData implements ViewsBulkOperationsViewDataInterfac
   protected $view;
 
   /**
-   * The realtionship ID.
+   * The relationship ID.
    *
    * @var string
    */
diff --git a/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php b/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php
index b809ebf464d75f89ed63eaf8c7f0a08537bf0601..97356a16fa3cac36b3a57cfb060fb4e2ef666d6f 100644
--- a/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php
+++ b/web/modules/views_bulk_operations/src/ViewsBulkOperationsBatch.php
@@ -77,7 +77,7 @@ public static function getList(array $data, array &$context) {
    * Save generated list to user tempstore.
    *
    * @param bool $success
-   *   Was the process successfull?
+   *   Was the process successful?
    * @param array $results
    *   Batch process results array.
    * @param array $operations
@@ -145,7 +145,7 @@ public static function operation(array $data, array &$context) {
    * Batch finished callback.
    *
    * @param bool $success
-   *   Was the process successfull?
+   *   Was the process successful?
    * @param array $results
    *   Batch process results array.
    * @param array $operations
diff --git a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php
index b008520b345e89065ee653f413b89622a363b430..3c700d685a5ff52159e1102c423f8da2b755ff0f 100644
--- a/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php
+++ b/web/modules/views_bulk_operations/tests/src/Functional/ViewsBulkOperationsBulkFormTest.php
@@ -56,47 +56,6 @@ protected function setUp() {
 
   }
 
-  /**
-   * Helper function to test a batch process.
-   *
-   * After checking if we're on a Batch API page,
-   * the iterations are executed, the finished page is opened
-   * and browser redirects to the final destination.
-   *
-   * NOTE: As of Drupal 8.4, functional test
-   * automatically redirects user through all Batch API pages,
-   * so this function is not longer needed.
-   */
-  protected function assertBatchProcess() {
-    // Get the current batch ID.
-    $current_url = $this->getUrl();
-    $q = substr($current_url, strrpos($current_url, '/') + 1);
-    $this->assertEquals('batch?', substr($q, 0, 6), 'We are on a Batch API page.');
-
-    preg_match('#id=([0-9]+)#', $q, $matches);
-    $batch_id = $matches[1];
-
-    // Proceed with the operations.
-    // Assumption: all operations will be completed within a single request.
-    // TODO: modify code to include an option when the assumption is false.
-    do {
-      $this->drupalGet('batch', [
-        'query' => [
-          'id' => $batch_id,
-          'op' => 'do_nojs',
-        ],
-      ]);
-    } while (FALSE);
-
-    // Get the finished page.
-    $this->drupalGet('batch', [
-      'query' => [
-        'id' => $batch_id,
-        'op' => 'finished',
-      ],
-    ]);
-  }
-
   /**
    * Tests the VBO bulk form with simple test action.
    */
diff --git a/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php b/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7263470640aa5648322ffbd247c2d5d95206d348
--- /dev/null
+++ b/web/modules/views_bulk_operations/tests/src/FunctionalJavascript/ViewsBulkOperationsBulkFormTest.php
@@ -0,0 +1,143 @@
+<?php
+
+namespace Drupal\Tests\views_bulk_operations\FunctionalJavaScript;
+
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+use Drupal\views_bulk_operations\Form\ViewsBulkOperationsFormTrait;
+
+/**
+ * @coversDefaultClass \Drupal\views_bulk_operations\Plugin\views\field\ViewsBulkOperationsBulkForm
+ * @group views_bulk_operations
+ */
+class ViewsBulkOperationsBulkFormTest extends WebDriverTestBase {
+
+  use ViewsBulkOperationsFormTrait;
+
+  const TEST_NODE_COUNT = 15;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stable';
+
+
+  /**
+   * The assert session.
+   *
+   * @var \Drupal\Tests\WebAssert
+   */
+  protected $assertSession;
+
+  /**
+   * The page element.
+   *
+   * @var \Behat\Mink\Element\DocumentElement
+   */
+  protected $page;
+
+
+  /**
+   * The selected indexes of rows.
+   *
+   * @var array
+   */
+  protected $selectedIndexes = [];
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'node',
+    'views',
+    'views_bulk_operations',
+    'views_bulk_operations_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create some nodes for testing.
+    $this->drupalCreateContentType(['type' => 'page']);
+    for ($i = 1; $i <= self::TEST_NODE_COUNT; $i++) {
+      $this->drupalCreateNode([
+        'type' => 'page',
+        'title' => 'Title ' . $i,
+      ]);
+    }
+    $admin_user = $this->drupalCreateUser(
+      [
+        'edit any page content',
+        'create page content',
+        'delete any page content',
+      ]);
+    $this->drupalLogin($admin_user);
+
+    $this->assertSession = $this->assertSession();
+    $this->page = $this->getSession()->getPage();
+
+    $this->drupalGet('/views-bulk-operations-test');
+
+    // Make sure a checkbox appears on all rows and the button exists.
+    for ($i = 0; $i < 4; $i++) {
+      $this->assertSession->fieldExists('edit-views-bulk-operations-bulk-form-' . $i);
+    }
+    $this->assertSession->buttonExists('Simple test action');
+
+    $this->selectedIndexes = [0, 1, 3];
+
+    foreach ($this->selectedIndexes as $selected_index) {
+      $this->page->checkField('edit-views-bulk-operations-bulk-form-' . $selected_index);
+    }
+
+  }
+
+  /**
+   * Tests the VBO bulk form without dynamic insertion.
+   */
+  public function testViewsBulkOperationsWithOutDynamicInsertion() {
+
+    $this->page->pressButton('Simple test action');
+
+    foreach ($this->selectedIndexes as $index) {
+      $this->assertSession->pageTextContains(sprintf('Test action (preconfig: Test setting, label: Title %s)', self::TEST_NODE_COUNT - $index));
+    }
+    $this->assertSession->pageTextContains(sprintf('Action processing results: Test (%s)', count($this->selectedIndexes)));
+
+  }
+
+  /**
+   * Tests the VBO bulk form with dynamic insertion.
+   *
+   * Nodes inserted right after selecting targeted row(s) of the view.
+   */
+  public function testViewsBulkOperationsWithDynamicInsertion() {
+
+    // Insert nodes.
+    $nodes = [];
+    for ($i = 100; $i < 100 + self::TEST_NODE_COUNT; $i++) {
+      $nodes[] = $this->drupalCreateNode([
+        'type' => 'page',
+        'title' => 'Title ' . $i,
+      ]);
+    }
+
+    $this->page->pressButton('Simple test action');
+
+    foreach ($this->selectedIndexes as $index) {
+      $this->assertSession->pageTextContains(sprintf('Test action (preconfig: Test setting, label: Title %s)', self::TEST_NODE_COUNT - $index));
+    }
+    $this->assertSession->pageTextContains(sprintf('Action processing results: Test (%s)', count($this->selectedIndexes)));
+
+    // Remove nodes inserted in the middle.
+    foreach ($nodes as $node) {
+      $node->delete();
+    }
+
+  }
+
+}
diff --git a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php
index 6c80cedd5d5f7bb0a46ce9d9215f3c005873d3f6..032e3a45cc72c81adcda62fc8d57a4682d906922 100644
--- a/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php
+++ b/web/modules/views_bulk_operations/tests/src/Kernel/ViewsBulkOperationsKernelTestBase.php
@@ -262,7 +262,7 @@ protected function executeAction(array $vbo_data) {
       $vbo_data['action_label'] = (string) $action_definition['label'];
     }
 
-    // Account for eclude mode.
+    // Account for exclude mode.
     if ($vbo_data['exclude_mode']) {
       $vbo_data['exclude_list'] = $vbo_data['list'];
       $vbo_data['list'] = [];
diff --git a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml
index d38540259e69437dd3496847f8d19f16546fd188..1d3513193f973e4882c9f1e188fbe7a971d8172b 100644
--- a/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml
+++ b/web/modules/views_bulk_operations/tests/views_bulk_operations_test/views_bulk_operations_test.info.yml
@@ -7,7 +7,7 @@ dependencies:
   - drupal:views_bulk_operations
   - drupal:node
 
-# Information added by Drupal.org packaging script on 2020-08-13
-version: '8.x-3.9'
+# Information added by Drupal.org packaging script on 2020-12-24
+version: '8.x-3.10'
 project: 'views_bulk_operations'
-datestamp: 1597319023
+datestamp: 1608795021
diff --git a/web/modules/views_bulk_operations/views_bulk_operations.drush.inc b/web/modules/views_bulk_operations/views_bulk_operations.drush.inc
index 4593d13f58f4cf606e594752219be1e9e158d597..8ac8bd17600bca0fbf10920e1312110187a7043d 100644
--- a/web/modules/views_bulk_operations/views_bulk_operations.drush.inc
+++ b/web/modules/views_bulk_operations/views_bulk_operations.drush.inc
@@ -22,7 +22,7 @@ function views_bulk_operations_drush_command() {
       ],
       'options' => [
         'display-id' => 'ID of the display to use (default: default)',
-        'args' => 'View arguments (slash is a delimeter, default: none)',
+        'args' => 'View arguments (slash is a delimiter, default: none)',
         'exposed' => 'Exposed filters (query string format)',
         'batch-size' => 'Processing batch size (default: 100)',
         'config' => 'Action configuration (query string format)',
@@ -77,7 +77,7 @@ function _views_bulk_operations_timer($debug = TRUE, $id = NULL) {
 }
 
 /**
- * The vbo-exec command executtion function.
+ * The vbo-exec command execution function.
  *
  * @param string $view_id
  *   The ID of the view to use.
diff --git a/web/modules/views_bulk_operations/views_bulk_operations.info.yml b/web/modules/views_bulk_operations/views_bulk_operations.info.yml
index 0c013455895cbb137b3edb4186600f914730ef73..9539dbe95dbe7a65e514e269b8d624345a6303cf 100644
--- a/web/modules/views_bulk_operations/views_bulk_operations.info.yml
+++ b/web/modules/views_bulk_operations/views_bulk_operations.info.yml
@@ -6,7 +6,7 @@ core_version_requirement: ^8.8 || ^9
 dependencies:
   - drupal:views
 
-# Information added by Drupal.org packaging script on 2020-08-13
-version: '8.x-3.9'
+# Information added by Drupal.org packaging script on 2020-12-24
+version: '8.x-3.10'
 project: 'views_bulk_operations'
-datestamp: 1597319023
+datestamp: 1608795021
diff --git a/web/modules/views_bulk_operations/views_bulk_operations.services.yml b/web/modules/views_bulk_operations/views_bulk_operations.services.yml
index ef64d9e476501b7c1a8bcd8589255fb65f2d2858..3937a3d0273cd1e5e8aa1014d5d22abe86fa31ff 100644
--- a/web/modules/views_bulk_operations/views_bulk_operations.services.yml
+++ b/web/modules/views_bulk_operations/views_bulk_operations.services.yml
@@ -7,7 +7,7 @@ services:
     arguments: ['@views_bulk_operations.data', '@plugin.manager.views_bulk_operations_action', '@current_user', '@module_handler']
   plugin.manager.views_bulk_operations_action:
     class: Drupal\views_bulk_operations\Service\ViewsBulkOperationsActionManager
-    arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@event_dispatcher']
+    arguments: ['@container.namespaces', '@cache.discovery', '@module_handler', '@event_dispatcher', '@entity_type.manager']
   views_bulk_operations.access:
     class: Drupal\views_bulk_operations\Access\ViewsBulkOperationsAccess
     arguments: ['@tempstore.private']