annotate core/tests/Drupal/FunctionalJavascriptTests/JSWebAssert.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\FunctionalJavascriptTests;
Chris@0 4
Chris@0 5 use Behat\Mink\Element\NodeElement;
Chris@0 6 use Behat\Mink\Exception\ElementHtmlException;
Chris@0 7 use Behat\Mink\Exception\ElementNotFoundException;
Chris@0 8 use Behat\Mink\Exception\UnsupportedDriverActionException;
Chris@0 9 use Drupal\Tests\WebAssert;
Chris@0 10
Chris@0 11 /**
Chris@0 12 * Defines a class with methods for asserting presence of elements during tests.
Chris@0 13 */
Chris@0 14 class JSWebAssert extends WebAssert {
Chris@0 15
Chris@0 16 /**
Chris@0 17 * Waits for AJAX request to be completed.
Chris@0 18 *
Chris@0 19 * @param int $timeout
Chris@0 20 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 21 * @param string $message
Chris@0 22 * (optional) A message for exception.
Chris@0 23 *
Chris@0 24 * @throws \RuntimeException
Chris@0 25 * When the request is not completed. If left blank, a default message will
Chris@0 26 * be displayed.
Chris@0 27 */
Chris@0 28 public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {
Chris@0 29 $condition = <<<JS
Chris@0 30 (function() {
Chris@0 31 function isAjaxing(instance) {
Chris@0 32 return instance && instance.ajaxing === true;
Chris@0 33 }
Chris@0 34 return (
Chris@0 35 // Assert no AJAX request is running (via jQuery or Drupal) and no
Chris@0 36 // animation is running.
Chris@0 37 (typeof jQuery === 'undefined' || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&
Chris@0 38 (typeof Drupal === 'undefined' || typeof Drupal.ajax === 'undefined' || !Drupal.ajax.instances.some(isAjaxing))
Chris@0 39 );
Chris@0 40 }());
Chris@0 41 JS;
Chris@0 42 $result = $this->session->wait($timeout, $condition);
Chris@0 43 if (!$result) {
Chris@0 44 throw new \RuntimeException($message);
Chris@0 45 }
Chris@0 46 }
Chris@0 47
Chris@0 48 /**
Chris@0 49 * Waits for the specified selector and returns it when available.
Chris@0 50 *
Chris@0 51 * @param string $selector
Chris@0 52 * The selector engine name. See ElementInterface::findAll() for the
Chris@0 53 * supported selectors.
Chris@0 54 * @param string|array $locator
Chris@0 55 * The selector locator.
Chris@0 56 * @param int $timeout
Chris@0 57 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 58 *
Chris@0 59 * @return \Behat\Mink\Element\NodeElement|null
Chris@0 60 * The page element node if found, NULL if not.
Chris@0 61 *
Chris@0 62 * @see \Behat\Mink\Element\ElementInterface::findAll()
Chris@0 63 */
Chris@0 64 public function waitForElement($selector, $locator, $timeout = 10000) {
Chris@0 65 $page = $this->session->getPage();
Chris@0 66
Chris@0 67 $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
Chris@0 68 return $page->find($selector, $locator);
Chris@0 69 });
Chris@0 70
Chris@0 71 return $result;
Chris@0 72 }
Chris@0 73
Chris@0 74 /**
Chris@0 75 * Waits for the specified selector and returns it when available and visible.
Chris@0 76 *
Chris@0 77 * @param string $selector
Chris@0 78 * The selector engine name. See ElementInterface::findAll() for the
Chris@0 79 * supported selectors.
Chris@0 80 * @param string|array $locator
Chris@0 81 * The selector locator.
Chris@0 82 * @param int $timeout
Chris@0 83 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 84 *
Chris@0 85 * @return \Behat\Mink\Element\NodeElement|null
Chris@0 86 * The page element node if found and visible, NULL if not.
Chris@0 87 *
Chris@0 88 * @see \Behat\Mink\Element\ElementInterface::findAll()
Chris@0 89 */
Chris@0 90 public function waitForElementVisible($selector, $locator, $timeout = 10000) {
Chris@0 91 $page = $this->session->getPage();
Chris@0 92
Chris@0 93 $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
Chris@0 94 $element = $page->find($selector, $locator);
Chris@0 95 if (!empty($element) && $element->isVisible()) {
Chris@0 96 return $element;
Chris@0 97 }
Chris@0 98 return NULL;
Chris@0 99 });
Chris@0 100
Chris@0 101 return $result;
Chris@0 102 }
Chris@0 103 /**
Chris@0 104 * Waits for a button (input[type=submit|image|button|reset], button) with
Chris@0 105 * specified locator and returns it.
Chris@0 106 *
Chris@0 107 * @param string $locator
Chris@0 108 * The button ID, value or alt string.
Chris@0 109 * @param int $timeout
Chris@0 110 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 111 *
Chris@0 112 * @return \Behat\Mink\Element\NodeElement|null
Chris@0 113 * The page element node if found, NULL if not.
Chris@0 114 */
Chris@0 115 public function waitForButton($locator, $timeout = 10000) {
Chris@0 116 return $this->waitForElement('named', ['button', $locator], $timeout);
Chris@0 117 }
Chris@0 118
Chris@0 119 /**
Chris@0 120 * Waits for a link with specified locator and returns it when available.
Chris@0 121 *
Chris@0 122 * @param string $locator
Chris@0 123 * The link ID, title, text or image alt.
Chris@0 124 * @param int $timeout
Chris@0 125 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 126 *
Chris@0 127 * @return \Behat\Mink\Element\NodeElement|null
Chris@0 128 * The page element node if found, NULL if not.
Chris@0 129 */
Chris@0 130 public function waitForLink($locator, $timeout = 10000) {
Chris@0 131 return $this->waitForElement('named', ['link', $locator], $timeout);
Chris@0 132 }
Chris@0 133
Chris@0 134 /**
Chris@0 135 * Waits for a field with specified locator and returns it when available.
Chris@0 136 *
Chris@0 137 * @param string $locator
Chris@0 138 * The input ID, name or label for the field (input, textarea, select).
Chris@0 139 * @param int $timeout
Chris@0 140 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 141 *
Chris@0 142 * @return \Behat\Mink\Element\NodeElement|null
Chris@0 143 * The page element node if found, NULL if not.
Chris@0 144 */
Chris@0 145 public function waitForField($locator, $timeout = 10000) {
Chris@0 146 return $this->waitForElement('named', ['field', $locator], $timeout);
Chris@0 147 }
Chris@0 148
Chris@0 149 /**
Chris@0 150 * Waits for an element by its id and returns it when available.
Chris@0 151 *
Chris@0 152 * @param string $id
Chris@0 153 * The element ID.
Chris@0 154 * @param int $timeout
Chris@0 155 * (Optional) Timeout in milliseconds, defaults to 10000.
Chris@0 156 *
Chris@0 157 * @return \Behat\Mink\Element\NodeElement|null
Chris@0 158 * The page element node if found, NULL if not.
Chris@0 159 */
Chris@0 160 public function waitForId($id, $timeout = 10000) {
Chris@0 161 return $this->waitForElement('named', ['id', $id], $timeout);
Chris@0 162 }
Chris@0 163
Chris@0 164 /**
Chris@0 165 * Waits for the jQuery autocomplete delay duration.
Chris@0 166 *
Chris@0 167 * @see https://api.jqueryui.com/autocomplete/#option-delay
Chris@0 168 */
Chris@0 169 public function waitOnAutocomplete() {
Chris@0 170 // Wait for the autocomplete to be visible.
Chris@0 171 return $this->waitForElementVisible('css', '.ui-autocomplete li');
Chris@0 172 }
Chris@0 173
Chris@0 174 /**
Chris@0 175 * Test that a node, or it's specific corner, is visible in the viewport.
Chris@0 176 *
Chris@0 177 * Note: Always set the viewport size. This can be done with a PhantomJS
Chris@0 178 * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
Chris@0 179 * Drupal CI Javascript tests by default use a viewport of 1024x768px.
Chris@0 180 *
Chris@0 181 * @param string $selector_type
Chris@0 182 * The element selector type (CSS, XPath).
Chris@0 183 * @param string|array $selector
Chris@0 184 * The element selector. Note: the first found element is used.
Chris@0 185 * @param bool|string $corner
Chris@0 186 * (Optional) The corner to test:
Chris@0 187 * topLeft, topRight, bottomRight, bottomLeft.
Chris@0 188 * Or FALSE to check the complete element (default).
Chris@0 189 * @param string $message
Chris@0 190 * (optional) A message for the exception.
Chris@0 191 *
Chris@0 192 * @throws \Behat\Mink\Exception\ElementHtmlException
Chris@0 193 * When the element doesn't exist.
Chris@0 194 * @throws \Behat\Mink\Exception\ElementNotFoundException
Chris@0 195 * When the element is not visible in the viewport.
Chris@0 196 */
Chris@0 197 public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
Chris@0 198 $node = $this->session->getPage()->find($selector_type, $selector);
Chris@0 199 if ($node === NULL) {
Chris@0 200 if (is_array($selector)) {
Chris@0 201 $selector = implode(' ', $selector);
Chris@0 202 }
Chris@0 203 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
Chris@0 204 }
Chris@0 205
Chris@0 206 // Check if the node is visible on the page, which is a prerequisite of
Chris@0 207 // being visible in the viewport.
Chris@0 208 if (!$node->isVisible()) {
Chris@0 209 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
Chris@0 210 }
Chris@0 211
Chris@0 212 $result = $this->checkNodeVisibilityInViewport($node, $corner);
Chris@0 213
Chris@0 214 if (!$result) {
Chris@0 215 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
Chris@0 216 }
Chris@0 217 }
Chris@0 218
Chris@0 219 /**
Chris@0 220 * Test that a node, or its specific corner, is not visible in the viewport.
Chris@0 221 *
Chris@0 222 * Note: the node should exist in the page, otherwise this assertion fails.
Chris@0 223 *
Chris@0 224 * @param string $selector_type
Chris@0 225 * The element selector type (CSS, XPath).
Chris@0 226 * @param string|array $selector
Chris@0 227 * The element selector. Note: the first found element is used.
Chris@0 228 * @param bool|string $corner
Chris@0 229 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
Chris@0 230 * Or FALSE to check the complete element (default).
Chris@0 231 * @param string $message
Chris@0 232 * (optional) A message for the exception.
Chris@0 233 *
Chris@0 234 * @throws \Behat\Mink\Exception\ElementHtmlException
Chris@0 235 * When the element doesn't exist.
Chris@0 236 * @throws \Behat\Mink\Exception\ElementNotFoundException
Chris@0 237 * When the element is not visible in the viewport.
Chris@0 238 *
Chris@0 239 * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
Chris@0 240 */
Chris@0 241 public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
Chris@0 242 $node = $this->session->getPage()->find($selector_type, $selector);
Chris@0 243 if ($node === NULL) {
Chris@0 244 if (is_array($selector)) {
Chris@0 245 $selector = implode(' ', $selector);
Chris@0 246 }
Chris@0 247 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
Chris@0 248 }
Chris@0 249
Chris@0 250 $result = $this->checkNodeVisibilityInViewport($node, $corner);
Chris@0 251
Chris@0 252 if ($result) {
Chris@0 253 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
Chris@0 254 }
Chris@0 255 }
Chris@0 256
Chris@0 257 /**
Chris@0 258 * Check the visibility of a node, or it's specific corner.
Chris@0 259 *
Chris@0 260 * @param \Behat\Mink\Element\NodeElement $node
Chris@0 261 * A valid node.
Chris@0 262 * @param bool|string $corner
Chris@0 263 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
Chris@0 264 * Or FALSE to check the complete element (default).
Chris@0 265 *
Chris@0 266 * @return bool
Chris@0 267 * Returns TRUE if the node is visible in the viewport, FALSE otherwise.
Chris@0 268 *
Chris@0 269 * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
Chris@0 270 * When an invalid corner specification is given.
Chris@0 271 */
Chris@0 272 private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
Chris@0 273 $xpath = $node->getXpath();
Chris@0 274
Chris@0 275 // Build the Javascript to test if the complete element or a specific corner
Chris@0 276 // is in the viewport.
Chris@0 277 switch ($corner) {
Chris@0 278 case 'topLeft':
Chris@0 279 $test_javascript_function = <<<JS
Chris@0 280 function t(r, lx, ly) {
Chris@0 281 return (
Chris@0 282 r.top >= 0 &&
Chris@0 283 r.top <= ly &&
Chris@0 284 r.left >= 0 &&
Chris@0 285 r.left <= lx
Chris@0 286 )
Chris@0 287 }
Chris@0 288 JS;
Chris@0 289 break;
Chris@0 290
Chris@0 291 case 'topRight':
Chris@0 292 $test_javascript_function = <<<JS
Chris@0 293 function t(r, lx, ly) {
Chris@0 294 return (
Chris@0 295 r.top >= 0 &&
Chris@0 296 r.top <= ly &&
Chris@0 297 r.right >= 0 &&
Chris@0 298 r.right <= lx
Chris@0 299 );
Chris@0 300 }
Chris@0 301 JS;
Chris@0 302 break;
Chris@0 303
Chris@0 304 case 'bottomRight':
Chris@0 305 $test_javascript_function = <<<JS
Chris@0 306 function t(r, lx, ly) {
Chris@0 307 return (
Chris@0 308 r.bottom >= 0 &&
Chris@0 309 r.bottom <= ly &&
Chris@0 310 r.right >= 0 &&
Chris@0 311 r.right <= lx
Chris@0 312 );
Chris@0 313 }
Chris@0 314 JS;
Chris@0 315 break;
Chris@0 316
Chris@0 317 case 'bottomLeft':
Chris@0 318 $test_javascript_function = <<<JS
Chris@0 319 function t(r, lx, ly) {
Chris@0 320 return (
Chris@0 321 r.bottom >= 0 &&
Chris@0 322 r.bottom <= ly &&
Chris@0 323 r.left >= 0 &&
Chris@0 324 r.left <= lx
Chris@0 325 );
Chris@0 326 }
Chris@0 327 JS;
Chris@0 328 break;
Chris@0 329
Chris@0 330 case FALSE:
Chris@0 331 $test_javascript_function = <<<JS
Chris@0 332 function t(r, lx, ly) {
Chris@0 333 return (
Chris@0 334 r.top >= 0 &&
Chris@0 335 r.left >= 0 &&
Chris@0 336 r.bottom <= ly &&
Chris@0 337 r.right <= lx
Chris@0 338 );
Chris@0 339 }
Chris@0 340 JS;
Chris@0 341 break;
Chris@0 342
Chris@0 343 // Throw an exception if an invalid corner parameter is given.
Chris@0 344 default:
Chris@0 345 throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
Chris@0 346 }
Chris@0 347
Chris@0 348 // Build the full Javascript test. The shared logic gets the corner
Chris@0 349 // specific test logic injected.
Chris@0 350 $full_javascript_visibility_test = <<<JS
Chris@0 351 (function(t){
Chris@0 352 var w = window,
Chris@0 353 d = document,
Chris@0 354 e = d.documentElement,
Chris@0 355 n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
Chris@0 356 r = n.getBoundingClientRect(),
Chris@0 357 lx = (w.innerWidth || e.clientWidth),
Chris@0 358 ly = (w.innerHeight || e.clientHeight);
Chris@0 359
Chris@0 360 return t(r, lx, ly);
Chris@0 361 }($test_javascript_function));
Chris@0 362 JS;
Chris@0 363
Chris@0 364 // Check the visibility by injecting and executing the full Javascript test
Chris@0 365 // script in the page.
Chris@0 366 return $this->session->evaluateScript($full_javascript_visibility_test);
Chris@0 367 }
Chris@0 368
Chris@0 369 }