diff --git a/src/Tests/FlagContextualLinksTest.php b/src/Tests/FlagContextualLinksTest.php
deleted file mode 100644
index 4d5f4a4..0000000
--- a/src/Tests/FlagContextualLinksTest.php
+++ /dev/null
@@ -1,167 +0,0 @@
-<?php
-
-namespace Drupal\flag\Tests;
-
-use Drupal\Component\Utility\UrlHelper;
-use Drupal\Component\Serialization\Json;
-use Drupal\Core\Url;
-use Drupal\node\NodeInterface;
-use Drupal\flag\FlagInterface;
-use Drupal\Component\Utility\Html;
-
-/**
- * Test the contextual links with Reload link type.
- *
- * @group flag
- */
-class FlagContextualLinksTest extends FlagTestBase {
-  /**
-   * The flag.
-   *
-   * @var \Drupal\flag\Flaginterface
-   */
-  protected $flag;
-
-  /**
-   * * An authenticated user to test flagging.
-   *
-   * @var \Drupal\user\Entity\User|false
-   */
-  protected $normalUser;
-
-  public function setUp() {
-    parent::setUp();
-
-    // Create a regular user who will be flagging content.
-    $this->normalUser = $this->drupalCreateUser();
-  }
-
-  /**
-   * Create a new flag with the Reload link type and enable contextual links display.
-   */
-  public function testNodeLinks() {
-    $this->doCreateFlag();
-    $this->doFlagNode();
-  }
-
-  /**
-   * Create a node type and flag.
-   */
-  public function doCreateFlag() {
-    $this->drupalLogin($this->adminUser);
-
-    $edit = [
-      'id' => 'test_label_123',
-      'show_as_field' => FALSE,
-      'show_contextual_link' => TRUE,
-    ];
-
-    $this->flag = $this->createFlagWithForm('node', $edit);
-  }
-
-  /**
-   * Create a node and flag it.
-   */
-  public function doFlagNode() {
-    // Grant the flag permissions to the authenticated role.
-    $this->grantFlagPermissions($this->flag);
-
-    $node = $this->drupalCreateNode(['type' => $this->nodeType]);
-
-    // Login as normal user.
-    $this->drupalLogin($this->normalUser);
-
-    // Open node view page that renders Full view mode.
-    $this->drupalGet('node/' . $node->id());
-
-    // Expect Contextual links id for this node to contain flag_keys metadata.
-    $contextual_links_id = 'node:node=' . $node->id() . ':changed=' . $node->getChangedTime() . '&flag_keys=' . $this->flag->id() . '-flag&langcode=en';
-    $this->assertContextualLinkPlaceHolder($contextual_links_id);
-
-    $flag_action_url = $this->getFlagContextualLinkUrl($this->flag, $node, $contextual_links_id, 'flag');
-    $this->assertTrue($flag_action_url, "Flag link found in contextual links.");
-
-    // Emulate click on flag link in Contextual links.
-    $this->drupalGet($flag_action_url);
-
-    // Expect Contextual links id for this node to contain flag_keys metadata with unflag action.
-    $contextual_links_id = 'node:node=' . $node->id() . ':changed=' . $node->getChangedTime() . '&flag_keys=' . $this->flag->id() . '-unflag&langcode=en';
-    $this->assertContextualLinkPlaceHolder($contextual_links_id);
-
-    $flag_action_url = $this->getFlagContextualLinkUrl($this->flag, $node, $contextual_links_id, 'unflag');
-    $this->assertTrue($flag_action_url, "Unflag link found in contextual links.");
-
-    // Login as normal user.
-    $this->drupalLogin($this->drupalCreateUser());
-
-    // Open node view page that renders Full view mode.
-    $this->drupalGet('node/' . $node->id());
-
-    // Expect Contextual links id for this node to contain flag_keys metadata.
-    $contextual_links_id = 'node:node=' . $node->id() . ':changed=' . $node->getChangedTime() . '&flag_keys=' . $this->flag->id() . '-flag&langcode=en';
-    $this->assertContextualLinkPlaceHolder($contextual_links_id);
-
-    $flag_action_url = $this->getFlagContextualLinkUrl($this->flag, $node, $contextual_links_id, 'flag');
-    $this->assertTrue($flag_action_url, "Flag link found in contextual links.");
-  }
-
-  /**
-   * Get server-rendered contextual links for the given contextual link ids.
-   *
-   * @see \Drupal\contextual\Tests\ContextualDynamicContextTest::renderContextualLinks().
-   * @param array $ids
-   *   An array of contextual link ids.
-   * @param string $current_path
-   *   The Drupal path for the page for which the contextual links are rendered.
-   *
-   * @return string
-   *   The response body.
-   */
-  protected function renderContextualLinks($ids, $current_path) {
-    $post = array();
-    for ($i = 0; $i < count($ids); $i++) {
-      $post['ids[' . $i . ']'] = $ids[$i];
-    }
-    return $this->drupalPostWithFormat('contextual/render', 'json', $post, array('query' => array('destination' => $current_path)));
-  }
-
-  /**
-   * Helper method that returns a flag action URL from contextual links markup
-   * by contextual links id.
-   *
-   * @param string $contextual_links_id
-   *   Contextual links placeholder ids string.
-   * @return \Drupal\Core\Url|FALSE
-   *   Returns either a flag action URL object or FALSE in case that flag link
-   *   wasn't found in the contextual links markup.
-   */
-  protected function getFlagContextualLinkUrl(FlagInterface $flag,
-                                              NodeInterface $node,
-                                              $contextual_links_id,
-                                              $flag_action) {
-    // Render contextual links JSON response and check for flag link.
-    $response = $this->renderContextualLinks([$contextual_links_id], 'node');
-    $this->assertResponse(200);
-    $json = Json::decode($response);
-    $flag_url = Url::fromRoute("flag.action_link_{$flag_action}", array(
-      'flag' => $flag->id(),
-      'entity_id' => $node->id(),
-    ));
-
-    $flag_short_text = $flag->getShortText($flag_action);
-
-    $matches = array();
-    $url_pattern =  preg_quote(base_path()) . '(' . preg_quote($flag_url->getInternalPath(), '@') . '[^"]*)';
-    $found = preg_match('@' . preg_quote('<ul class="contextual-links"><li class="flag-test-label-123"><a href="') .
-      $url_pattern . preg_quote('">' . Html::escape($flag_short_text) . '</a></li></ul>', '@') . '@',
-      $json[$contextual_links_id], $matches);
-
-    if ($found) {
-      $options = UrlHelper::parse($matches[1]);
-      return Url::fromUri('internal:/' . $options['path'], $options);
-    }
-
-    return FALSE;
-  }
-
-}
diff --git a/src/Tests/FlagTestBase.php b/src/Tests/FlagTestBase.php
index 2cdf91c..fee1c00 100644
--- a/src/Tests/FlagTestBase.php
+++ b/src/Tests/FlagTestBase.php
@@ -5,6 +5,7 @@ namespace Drupal\flag\Tests;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\simpletest\WebTestBase;
 use Drupal\flag\Entity\Flag;
+use Drupal\Tests\flag\Traits\FlagPermissionsTrait;
 use Drupal\Core\Template\Attribute;
 
 /**
diff --git a/tests/src/Functional/ShowOnEntityFormTest.php b/tests/src/Functional/ShowOnEntityFormTest.php
index 3582bb5..0603517 100644
--- a/tests/src/Functional/ShowOnEntityFormTest.php
+++ b/tests/src/Functional/ShowOnEntityFormTest.php
@@ -3,7 +3,7 @@
 namespace Drupal\Tests\flag\Functional;
 
 use Drupal\flag\Tests\FlagCreateTrait;
-use Drupal\flag\Tests\FlagPermissionsTrait;
+use Drupal\Tests\flag\Traits\FlagPermissionsTrait;
 use Drupal\Tests\BrowserTestBase;
 
 /**
diff --git a/tests/src/FunctionalJavascript/FlagContextualLinksTest.php b/tests/src/FunctionalJavascript/FlagContextualLinksTest.php
new file mode 100755
index 0000000..301debb
--- /dev/null
+++ b/tests/src/FunctionalJavascript/FlagContextualLinksTest.php
@@ -0,0 +1,234 @@
+<?php
+
+namespace Drupal\Tests\flag\FunctionalJavascript;
+
+use Drupal\flag\Entity\Flag;
+use Drupal\flag\Tests\FlagCreateTrait;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\Tests\flag\Traits\FlagPermissionsTrait;
+
+/**
+ * Test the contextual links with Reload link type.
+ *
+ * PHPUnit serialises all the $GLOBALS in the system for ::assertLinkMatches
+ * to work the backing up of globals need to be disabled.
+ *
+ * @backupGlobals disabled
+ *
+ * @group flag
+ */
+class FlagContextualLinksTest extends JavascriptTestBase {
+
+  use FlagCreateTrait;
+  use FlagPermissionsTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'contextual',
+    'field_ui',
+    'flag',
+    'node',
+    'system',
+    'toolbar',
+    'user',
+  ];
+
+  /**
+   * The flag.
+   *
+   * @var \Drupal\flag\Flaginterface
+   */
+  protected $flag;
+
+  /**
+   * A user with Flag admin rights.
+   *
+   * @var AccountInterface
+   */
+  protected $adminUser;
+
+  /**
+   * An authenticated user to test flagging.
+   *
+   * @var \Drupal\user\Entity\User|false
+   */
+  protected $alice;
+
+  /**
+   * An authenticated user to test flagging.
+   *
+   * Used to test cache contexts.
+   *
+   * @var \Drupal\user\Entity\User|false
+   */
+  protected $bob;
+
+  /**
+   * The node type to use in the test.
+   *
+   * @var string
+   */
+  protected $nodeType = 'article';
+
+  /**
+   * The flag service.
+   *
+   * @var \Drupal\flag\FlagServiceInterface
+   */
+  protected $flagService;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Get the Flag Service.
+    $this->flagService = $this->container->get('flag');
+
+    // Create content type.
+    $this->drupalCreateContentType(['type' => $this->nodeType]);
+
+    // Create the admin user.
+    $this->adminUser = $this->createUser([
+      'administer flags',
+      'administer flagging display',
+      'administer flagging fields',
+      'administer node display',
+      'administer modules',
+      'administer nodes',
+      'create ' . $this->nodeType . ' content',
+      'edit any ' . $this->nodeType . ' content',
+      'delete any ' . $this->nodeType . ' content',
+      'access contextual links',
+      'access content',
+      'access toolbar',
+    ]);
+
+    // Create a regular user who will be flagging content.
+    $this->alice = $this->drupalCreateUser([
+      'create ' . $this->nodeType . ' content',
+      'edit any ' . $this->nodeType . ' content',
+      'delete any ' . $this->nodeType . ' content',
+      'access contextual links',
+      'access content',
+      'access toolbar',
+    ]);
+
+    // Create a second regular user who will be flagging content.
+    $this->bob = $this->drupalCreateUser([
+      'create ' . $this->nodeType . ' content',
+      'edit any ' . $this->nodeType . ' content',
+      'delete any ' . $this->nodeType . ' content',
+      'access contextual links',
+      'access content',
+      'access toolbar',
+    ]);
+
+    $this->drupalLogin($this->adminUser);
+
+    // Create flag with a Reload link type and enable contextual links display.
+    $this->flag = Flag::create([
+      'id' => 'test_label_123',
+      'label' => $this->randomString(),
+      'entity_type' => 'node',
+      'bundles' => array_keys(\Drupal::service('entity_type.bundle.info')->getBundleInfo('node')),
+      'flag_short' => $this->randomString(),
+      'unflag_short' => $this->randomString(),
+      'unflag_denied_text' => $this->randomString(),
+      'flag_long' => $this->randomString(16),
+      'unflag_long' => $this->randomString(16),
+      'flag_message' => $this->randomString(32),
+      'unflag_message' => $this->randomString(32),
+      'flag_type' => $this->getFlagType('node'),
+      'link_type' => 'reload',
+      'flagTypeConfig' => [
+        'show_as_field' => FALSE,
+        'show_on_form' => FALSE,
+        'show_contextual_link' => TRUE,
+      ],
+      'linkTypeConfig' => [],
+      'global' => FALSE,
+    ]);
+    $this->flag->save();
+
+    // Grant the flag permissions to the authenticated role.
+    $this->grantFlagPermissions($this->flag);
+
+  }
+
+  /**
+   * Verify the behaviour and rendering of Flag links.
+   *
+   * @runInSeparateProcess
+   */
+  public function testFlagLinks() {
+    $assert_session = $this->assertSession();
+
+    // Create an article, to which our contextual link will be attached.
+    $node = $this->drupalCreateNode(['type' => $this->nodeType]);
+
+    // Login as normal user.
+    $this->drupalLogin($this->alice);
+
+    // Open node view page that renders Full view mode.
+    $this->drupalGet('node/' . $node->id());
+
+    // Click on the edit button to become active.
+    $main_contextual_button = $assert_session
+      ->waitForElementVisible('css', 'button:contains("Edit")');
+
+    $this->assertNotNull($main_contextual_button);
+    $main_contextual_button->click();
+
+    // Expect the contextual links identifer for this node to contain flag_keys
+    // metadata related to the flag action.
+    $flag_contextual_links_id = 'node:node=' . $node->id() . ':changed=' . $node->getChangedTime() . '&flag_keys=' . $this->flag->id() . '-flag&langcode=en';
+
+    // Wait for the article contextual link button to appear.
+    $contextual_edit = $assert_session
+      ->waitForElementVisible('css', 'div[data-contextual-id="' . $flag_contextual_links_id . '"] button');
+    $this->assertNotNull($contextual_edit, format_string('Contextual link placeholder with id @id exists (flag).', ['@id' => $flag_contextual_links_id]));
+
+    $contextual_edit->click();
+
+    // The contextual link dialog will appear .. containing a flag link.
+    $flag_link1 = $assert_session->waitForLink($this->flag->getShortText('flag'));
+    $this->assertNotNull($flag_link1);
+
+    $flag_link1->click();
+
+    // Verify the contextual links data are updated to contain unflag links.
+    $unflag_contextual_links_id = 'node:node=' . $node->id() . ':changed=' . $node->getChangedTime() . '&flag_keys=' . $this->flag->id() . '-unflag&langcode=en';
+    $contextual_edit2 = $assert_session
+      ->waitForElementVisible('css', 'div[data-contextual-id="' . $unflag_contextual_links_id . '"]  button');
+    $this->assertNotNull($contextual_edit2, format_string('Contextual link placeholder with id @id exists (unflag).', ['@id' => $unflag_contextual_links_id]));
+
+    $contextual_edit2->click();
+
+    // The contextual link dialog will appear .. containing a unflag link.
+    $unflag_link1 = $assert_session->waitForLink($this->flag->getShortText('unflag'));
+    $this->assertNotNull($unflag_link1);
+
+    // Login as alternate regular user.
+    // to verify that the cache context displayed to bob is different.
+    $this->drupalLogin($this->bob);
+
+    // Open node view page that renders Full view mode.
+    $this->drupalGet('node/' . $node->id());
+
+    // Expect the contextual links identifier for this node to contain flag_keys
+    // metadata related to the flag action.
+    $contextual_edit3 = $assert_session
+      ->waitForElementVisible('css', 'div[data-contextual-id="' . $flag_contextual_links_id . '"] button');
+    $this->assertNotNull($contextual_edit3, format_string('Contextual link placeholder with id @id exists.', ['@id' => $flag_contextual_links_id]));
+    $contextual_edit3->click();
+
+    // The contextual link dialog will appear .. containing a flag link.
+    $flag_link2 = $assert_session->waitForLink($this->flag->getShortText('flag'));
+    $this->assertNotNull($flag_link2);
+  }
+
+}
diff --git a/src/Tests/FlagPermissionsTrait.php b/tests/src/Traits/FlagPermissionsTrait.php
similarity index 97%
rename from src/Tests/FlagPermissionsTrait.php
rename to tests/src/Traits/FlagPermissionsTrait.php
index aec7853..a8206e7 100644
--- a/src/Tests/FlagPermissionsTrait.php
+++ b/tests/src/Traits/FlagPermissionsTrait.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\flag\Tests;
+namespace Drupal\Tests\flag\Traits;
 
 use Drupal\user\Entity\Role;
 use Drupal\user\RoleInterface;
