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..12c36cb
--- /dev/null
+++ b/tests/src/FunctionalJavascript/FlagContextualLinksTest.php
@@ -0,0 +1,246 @@
+<?php
+
+namespace Drupal\Tests\flag\FunctionalJavascript;
+
+use Drupal\core\Url;
+use Behat\Mink\Element\ElementInterface;
+use Drupal\flag\Entity\Flag;
+use Drupal\flag\FlagInterface;
+use Drupal\flag\Tests\FlagCreateTrait;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\node\NodeInterface;
+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() {
+    $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());
+
+    $assert_session = $this->assertSession();
+
+    // 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';
+
+    // Verify the link is as expecected.
+    $flag_link = $assert_session
+      ->waitForElement('css', 'div[data-contextual-id="' . $flag_contextual_links_id . '"] ul.contextual-links li.flag-test-label-123 a');
+    $this->assertNotNull($flag_link, format_string('Contextual link placeholder with id @id exists (flag).', ['@id' => $flag_contextual_links_id]));
+    $this->assertLinkMatches($flag_link, $this->flag, $node, 'flag');
+
+    // Simulate clicking on the flag contextual link.
+    //
+    // Zumba\GastonJS\Browser does not support the handling of JSON response
+    // objects so instead of clicking we programatically change the state of the
+    // flag and reload the page.
+    $this->flagService->flag($this->flag, $node, $this->alice);
+    $this->getSession()->reload();
+
+    // Verify the contextual links 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';
+    $unflag_link = $assert_session
+      ->waitForELement('css', 'div[data-contextual-id="' . $unflag_contextual_links_id . '"]  ul.contextual-links li.flag-test-label-123 a');
+    $this->assertNotNull($unflag_link, format_string('Contextual link placeholder with id @id exists (unflag).', ['@id' => $unflag_contextual_links_id]));
+    $this->assertLinkMatches($unflag_link, $this->flag, $node, 'unflag');
+
+    // 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.
+    $flag_link2 = $assert_session
+      ->waitForElement('css', 'div[data-contextual-id="' . $flag_contextual_links_id . '"] ul.contextual-links li.flag-test-label-123 a');
+    $this->assertNotNull($flag_link2, format_string('Contextual link placeholder with id @id exists.', ['@id' => $flag_contextual_links_id]));
+    $this->assertLinkMatches($flag_link2, $this->flag, $node, 'flag');
+  }
+
+  /**
+   * Returns a flag action contextual link in the form of a URL.
+   *
+   * A contextual identifier is needed to fully specify the final markup.
+   *
+   * @param Behat\Mink\Element\ElementInterface $link
+   *   The HTML elements wrapper holding the contextual links.
+   * @param \Drupal\flag\FlagInterface $flag
+   *   The flag associated with the contextual link.
+   * @param Drupal\node\NodeInterface $node
+   *   The node associated with the contextual link.
+   */
+  protected function assertLinkMatches(ElementInterface $link, FlagInterface $flag, NodeInterface $node, $action) {
+    // Extract href from the page.
+    $href = $link->getAttribute('href');
+
+    $flag_url = Url::fromRoute("flag.action_link_{$action}", [
+      'flag' => $flag->id(),
+      'entity_id' => $node->id(),
+    ]);
+
+    $this->assertEquals($href, $flag_url->toString(), 'A link was displayed macthing the given Flag and node pair.');
+  }
+
+}
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;
