You are here

class JSWebAssert in Drupal 8

Same name and namespace in other branches
  1. 9 core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php \Drupal\FunctionalJavascriptTests\JSWebAssert

Defines a class with methods for asserting presence of elements during tests.

Hierarchy

  • class \Drupal\Tests\WebAssert extends \Behat\Mink\WebAssert

Expanded class hierarchy of JSWebAssert

File

core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php, line 14

Namespace

Drupal\FunctionalJavascriptTests
View source
class JSWebAssert extends WebAssert {

  /**
   * Waits for AJAX request to be completed.
   *
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   * @param string $message
   *   (optional) A message for exception.
   *
   * @throws \RuntimeException
   *   When the request is not completed. If left blank, a default message will
   *   be displayed.
   */
  public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {
    $condition = <<<JS
      (function() {
        function isAjaxing(instance) {
          return instance && instance.ajaxing === true;
        }
        return (
          // Assert no AJAX request is running (via jQuery or Drupal) and no
          // animation is running.
          (typeof jQuery === 'undefined' || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&
          (typeof Drupal === 'undefined' || typeof Drupal.ajax === 'undefined' || !Drupal.ajax.instances.some(isAjaxing))
        );
      }());
JS;
    $result = $this->session
      ->wait($timeout, $condition);
    if (!$result) {
      throw new \RuntimeException($message);
    }
  }

  /**
   * Waits for the specified selector and returns it when available.
   *
   * @param string $selector
   *   The selector engine name. See ElementInterface::findAll() for the
   *   supported selectors.
   * @param string|array $locator
   *   The selector locator.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found, NULL if not.
   *
   * @see \Behat\Mink\Element\ElementInterface::findAll()
   */
  public function waitForElement($selector, $locator, $timeout = 10000) {
    $page = $this->session
      ->getPage();
    $result = $page
      ->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
      return $page
        ->find($selector, $locator);
    });
    return $result;
  }

  /**
   * Looks for the specified selector and returns TRUE when it is unavailable.
   *
   * @param string $selector
   *   The selector engine name. See ElementInterface::findAll() for the
   *   supported selectors.
   * @param string|array $locator
   *   The selector locator.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return bool
   *   TRUE if not found, FALSE if found.
   *
   * @see \Behat\Mink\Element\ElementInterface::findAll()
   */
  public function waitForElementRemoved($selector, $locator, $timeout = 10000) {
    $page = $this->session
      ->getPage();
    $result = $page
      ->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
      return !$page
        ->find($selector, $locator);
    });
    return $result;
  }

  /**
   * Waits for the specified selector and returns it when available and visible.
   *
   * @param string $selector
   *   The selector engine name. See ElementInterface::findAll() for the
   *   supported selectors.
   * @param string|array $locator
   *   The selector locator.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found and visible, NULL if not.
   *
   * @see \Behat\Mink\Element\ElementInterface::findAll()
   */
  public function waitForElementVisible($selector, $locator, $timeout = 10000) {
    $page = $this->session
      ->getPage();
    $result = $page
      ->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
      $element = $page
        ->find($selector, $locator);
      if (!empty($element) && $element
        ->isVisible()) {
        return $element;
      }
      return NULL;
    });
    return $result;
  }

  /**
   * Waits for the specified text and returns its element when available.
   *
   * @param string $text
   *   The text to wait for.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found and visible, NULL if not.
   */
  public function waitForText($text, $timeout = 10000) {
    $page = $this->session
      ->getPage();
    return $page
      ->waitFor($timeout / 1000, function () use ($page, $text) {
      $actual = preg_replace('/\\s+/u', ' ', $page
        ->getText());
      $regex = '/' . preg_quote($text, '/') . '/ui';
      return (bool) preg_match($regex, $actual);
    });
  }

  /**
   * Waits for a button (input[type=submit|image|button|reset], button) with
   * specified locator and returns it.
   *
   * @param string $locator
   *   The button ID, value or alt string.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found, NULL if not.
   */
  public function waitForButton($locator, $timeout = 10000) {
    return $this
      ->waitForElement('named', [
      'button',
      $locator,
    ], $timeout);
  }

  /**
   * Waits for a link with specified locator and returns it when available.
   *
   * @param string $locator
   *   The link ID, title, text or image alt.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found, NULL if not.
   */
  public function waitForLink($locator, $timeout = 10000) {
    return $this
      ->waitForElement('named', [
      'link',
      $locator,
    ], $timeout);
  }

  /**
   * Waits for a field with specified locator and returns it when available.
   *
   * @param string $locator
   *   The input ID, name or label for the field (input, textarea, select).
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found, NULL if not.
   */
  public function waitForField($locator, $timeout = 10000) {
    return $this
      ->waitForElement('named', [
      'field',
      $locator,
    ], $timeout);
  }

  /**
   * Waits for an element by its id and returns it when available.
   *
   * @param string $id
   *   The element ID.
   * @param int $timeout
   *   (Optional) Timeout in milliseconds, defaults to 10000.
   *
   * @return \Behat\Mink\Element\NodeElement|null
   *   The page element node if found, NULL if not.
   */
  public function waitForId($id, $timeout = 10000) {
    return $this
      ->waitForElement('named', [
      'id',
      $id,
    ], $timeout);
  }

  /**
   * Waits for the jQuery autocomplete delay duration.
   *
   * @see https://api.jqueryui.com/autocomplete/#option-delay
   */
  public function waitOnAutocomplete() {

    // Wait for the autocomplete to be visible.
    return $this
      ->waitForElementVisible('css', '.ui-autocomplete li');
  }

  /**
   * Test that a node, or its specific corner, is visible in the viewport.
   *
   * Note: Always set the viewport size. This can be done with a PhantomJS
   * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
   * Drupal CI Javascript tests by default use a viewport of 1024x768px.
   *
   * @param string $selector_type
   *   The element selector type (CSS, XPath).
   * @param string|array $selector
   *   The element selector. Note: the first found element is used.
   * @param bool|string $corner
   *   (Optional) The corner to test:
   *   topLeft, topRight, bottomRight, bottomLeft.
   *   Or FALSE to check the complete element (default).
   * @param string $message
   *   (optional) A message for the exception.
   *
   * @throws \Behat\Mink\Exception\ElementHtmlException
   *   When the element doesn't exist.
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   *   When the element is not visible in the viewport.
   */
  public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
    $node = $this->session
      ->getPage()
      ->find($selector_type, $selector);
    if ($node === NULL) {
      if (is_array($selector)) {
        $selector = implode(' ', $selector);
      }
      throw new ElementNotFoundException($this->session
        ->getDriver(), 'element', $selector_type, $selector);
    }

    // Check if the node is visible on the page, which is a prerequisite of
    // being visible in the viewport.
    if (!$node
      ->isVisible()) {
      throw new ElementHtmlException($message, $this->session
        ->getDriver(), $node);
    }
    $result = $this
      ->checkNodeVisibilityInViewport($node, $corner);
    if (!$result) {
      throw new ElementHtmlException($message, $this->session
        ->getDriver(), $node);
    }
  }

  /**
   * Test that a node, or its specific corner, is not visible in the viewport.
   *
   * Note: the node should exist in the page, otherwise this assertion fails.
   *
   * @param string $selector_type
   *   The element selector type (CSS, XPath).
   * @param string|array $selector
   *   The element selector. Note: the first found element is used.
   * @param bool|string $corner
   *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
   *   Or FALSE to check the complete element (default).
   * @param string $message
   *   (optional) A message for the exception.
   *
   * @throws \Behat\Mink\Exception\ElementHtmlException
   *   When the element doesn't exist.
   * @throws \Behat\Mink\Exception\ElementNotFoundException
   *   When the element is not visible in the viewport.
   *
   * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
   */
  public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
    $node = $this->session
      ->getPage()
      ->find($selector_type, $selector);
    if ($node === NULL) {
      if (is_array($selector)) {
        $selector = implode(' ', $selector);
      }
      throw new ElementNotFoundException($this->session
        ->getDriver(), 'element', $selector_type, $selector);
    }
    $result = $this
      ->checkNodeVisibilityInViewport($node, $corner);
    if ($result) {
      throw new ElementHtmlException($message, $this->session
        ->getDriver(), $node);
    }
  }

  /**
   * Check the visibility of a node, or its specific corner.
   *
   * @param \Behat\Mink\Element\NodeElement $node
   *   A valid node.
   * @param bool|string $corner
   *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
   *   Or FALSE to check the complete element (default).
   *
   * @return bool
   *   Returns TRUE if the node is visible in the viewport, FALSE otherwise.
   *
   * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
   *   When an invalid corner specification is given.
   */
  private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
    $xpath = $node
      ->getXpath();

    // Build the Javascript to test if the complete element or a specific corner
    // is in the viewport.
    switch ($corner) {
      case 'topLeft':
        $test_javascript_function = <<<JS
          function t(r, lx, ly) {
            return (
              r.top >= 0 &&
              r.top <= ly &&
              r.left >= 0 &&
              r.left <= lx
            )
          }
JS;
        break;
      case 'topRight':
        $test_javascript_function = <<<JS
          function t(r, lx, ly) {
            return (
              r.top >= 0 &&
              r.top <= ly &&
              r.right >= 0 &&
              r.right <= lx
            );
          }
JS;
        break;
      case 'bottomRight':
        $test_javascript_function = <<<JS
          function t(r, lx, ly) {
            return (
              r.bottom >= 0 &&
              r.bottom <= ly &&
              r.right >= 0 &&
              r.right <= lx
            );
          }
JS;
        break;
      case 'bottomLeft':
        $test_javascript_function = <<<JS
          function t(r, lx, ly) {
            return (
              r.bottom >= 0 &&
              r.bottom <= ly &&
              r.left >= 0 &&
              r.left <= lx
            );
          }
JS;
        break;
      case FALSE:
        $test_javascript_function = <<<JS
          function t(r, lx, ly) {
            return (
              r.top >= 0 &&
              r.left >= 0 &&
              r.bottom <= ly &&
              r.right <= lx
            );
          }
JS;
        break;

      // Throw an exception if an invalid corner parameter is given.
      default:
        throw new UnsupportedDriverActionException($corner, $this->session
          ->getDriver());
    }

    // Build the full Javascript test. The shared logic gets the corner
    // specific test logic injected.
    $full_javascript_visibility_test = <<<JS
      (function(t){
        var w = window,
        d = document,
        e = d.documentElement,
        n = d.evaluate("{<span class="php-variable">$xpath</span>}", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
        r = n.getBoundingClientRect(),
        lx = (w.innerWidth || e.clientWidth),
        ly = (w.innerHeight || e.clientHeight);

        return t(r, lx, ly);
      }({<span class="php-variable">$test_javascript_function</span>}));
JS;

    // Check the visibility by injecting and executing the full Javascript test
    // script in the page.
    return $this->session
      ->evaluateScript($full_javascript_visibility_test);
  }

  /**
   * Passes if the raw text IS NOT found escaped on the loaded page.
   *
   * Raw text refers to the raw HTML that the page generated.
   *
   * @param string $raw
   *   Raw (HTML) string to look for.
   */
  public function assertNoEscaped($raw) {
    $this
      ->responseNotContains($this
      ->escapeHtml($raw));
  }

  /**
   * Passes if the raw text IS found escaped on the loaded page.
   *
   * Raw text refers to the raw HTML that the page generated.
   *
   * @param string $raw
   *   Raw (HTML) string to look for.
   */
  public function assertEscaped($raw) {
    $this
      ->responseContains($this
      ->escapeHtml($raw));
  }

  /**
   * Escapes HTML for testing.
   *
   * Drupal's Html::escape() uses the ENT_QUOTES flag with htmlspecialchars() to
   * escape both single and double quotes. With JavascriptTestBase testing the
   * browser is automatically converting &quot; and &#039; to double and single
   * quotes respectively therefore we can not escape them when testing for
   * escaped HTML.
   *
   * @param $raw
   *   The raw string to escape.
   *
   * @return string
   *   The string with escaped HTML.
   *
   * @see Drupal\Component\Utility\Html::escape()
   */
  protected function escapeHtml($raw) {
    return htmlspecialchars($raw, ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8');
  }

  /**
   * Asserts that no matching element exists on the page after a wait.
   *
   * @param string $selector_type
   *   The element selector type (css, xpath).
   * @param string|array $selector
   *   The element selector.
   * @param int $timeout
   *   (optional) Timeout in milliseconds, defaults to 10000.
   * @param string $message
   *   (optional) The exception message.
   *
   * @throws \Behat\Mink\Exception\ElementHtmlException
   *   When an element still exists on the page.
   */
  public function assertNoElementAfterWait($selector_type, $selector, $timeout = 10000, $message = 'Element exists on the page.') {
    $start = microtime(TRUE);
    $end = $start + $timeout / 1000;
    $page = $this->session
      ->getPage();
    do {
      $node = $page
        ->find($selector_type, $selector);
      if (empty($node)) {
        return;
      }
      usleep(100000);
    } while (microtime(TRUE) < $end);
    throw new ElementHtmlException($message, $this->session
      ->getDriver(), $node);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
JSWebAssert::assertEscaped public function Passes if the raw text IS found escaped on the loaded page. Overrides WebAssert::assertEscaped
JSWebAssert::assertNoElementAfterWait public function Asserts that no matching element exists on the page after a wait.
JSWebAssert::assertNoEscaped public function Passes if the raw text IS NOT found escaped on the loaded page. Overrides WebAssert::assertNoEscaped
JSWebAssert::assertNotVisibleInViewport public function Test that a node, or its specific corner, is not visible in the viewport.
JSWebAssert::assertVisibleInViewport public function Test that a node, or its specific corner, is visible in the viewport.
JSWebAssert::assertWaitOnAjaxRequest public function Waits for AJAX request to be completed.
JSWebAssert::checkNodeVisibilityInViewport private function Check the visibility of a node, or its specific corner.
JSWebAssert::escapeHtml protected function Escapes HTML for testing.
JSWebAssert::waitForButton public function Waits for a button (input[type=submit|image|button|reset], button) with specified locator and returns it.
JSWebAssert::waitForElement public function Waits for the specified selector and returns it when available.
JSWebAssert::waitForElementRemoved public function Looks for the specified selector and returns TRUE when it is unavailable.
JSWebAssert::waitForElementVisible public function Waits for the specified selector and returns it when available and visible.
JSWebAssert::waitForField public function Waits for a field with specified locator and returns it when available.
JSWebAssert::waitForId public function Waits for an element by its id and returns it when available.
JSWebAssert::waitForLink public function Waits for a link with specified locator and returns it when available.
JSWebAssert::waitForText public function Waits for the specified text and returns its element when available.
JSWebAssert::waitOnAutocomplete public function Waits for the jQuery autocomplete delay duration.
WebAssert::$baseUrl protected property The absolute URL of the site under test.
WebAssert::assert public function Asserts a condition.
WebAssert::buildXPathQuery public function Builds an XPath query.
WebAssert::buttonExists public function Checks that specific button exists on the current page.
WebAssert::buttonNotExists public function Checks that the specific button does NOT exist on the current page.
WebAssert::cleanUrl protected function Trims scriptname from the URL.
WebAssert::fieldDisabled public function Checks that a given form field element is disabled.
WebAssert::fieldEnabled public function Checks that a given form field element is enabled.
WebAssert::hiddenFieldExists public function Checks that specific hidden field exists.
WebAssert::hiddenFieldNotExists public function Checks that specific hidden field does not exist.
WebAssert::hiddenFieldValueEquals public function Checks that specific hidden field have provided value.
WebAssert::hiddenFieldValueNotEquals public function Checks that specific hidden field doesn't have the provided value.
WebAssert::linkByHrefExists public function Passes if a link containing a given href (part) is found.
WebAssert::linkByHrefNotExists public function Passes if a link containing a given href (part) is not found.
WebAssert::linkExists public function Passes if a link with the specified label is found.
WebAssert::linkExistsExact public function Passes if a link with the exactly specified label is found.
WebAssert::linkNotExists public function Passes if a link with the specified label is not found.
WebAssert::linkNotExistsExact public function Passes if a link with the exactly specified label is not found.
WebAssert::optionExists public function Checks that specific option in a select field exists on the current page.
WebAssert::optionNotExists public function Checks that an option in a select field does NOT exist on the current page.
WebAssert::pageTextContainsOnce public function Checks that current page contains text only once.
WebAssert::responseContains public function Checks that page HTML (response content) contains text.
WebAssert::responseNotContains public function Checks that page HTML (response content) does not contains text.
WebAssert::selectExists public function Checks that specific select field exists on the current page.
WebAssert::titleEquals public function Pass if the page title is the given string.
WebAssert::__construct public function Constructor.