3 namespace Drupal\FunctionalJavascriptTests;
5 use Behat\Mink\Element\NodeElement;
6 use Behat\Mink\Exception\ElementHtmlException;
7 use Behat\Mink\Exception\ElementNotFoundException;
8 use Behat\Mink\Exception\UnsupportedDriverActionException;
9 use Drupal\Tests\WebAssert;
12 * Defines a class with methods for asserting presence of elements during tests.
14 class JSWebAssert extends WebAssert {
17 * Waits for AJAX request to be completed.
20 * (Optional) Timeout in milliseconds, defaults to 10000.
21 * @param string $message
22 * (optional) A message for exception.
24 * @throws \RuntimeException
25 * When the request is not completed. If left blank, a default message will
28 public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {
31 function isAjaxing(instance) {
32 return instance && instance.ajaxing === true;
35 // Assert no AJAX request is running (via jQuery or Drupal) and no
36 // animation is running.
37 (typeof jQuery === 'undefined' || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&
38 (typeof Drupal === 'undefined' || typeof Drupal.ajax === 'undefined' || !Drupal.ajax.instances.some(isAjaxing))
42 $result = $this->session->wait($timeout, $condition);
44 throw new \RuntimeException($message);
49 * Waits for the specified selector and returns it when available.
51 * @param string $selector
52 * The selector engine name. See ElementInterface::findAll() for the
53 * supported selectors.
54 * @param string|array $locator
55 * The selector locator.
57 * (Optional) Timeout in milliseconds, defaults to 10000.
59 * @return \Behat\Mink\Element\NodeElement|null
60 * The page element node if found, NULL if not.
62 * @see \Behat\Mink\Element\ElementInterface::findAll()
64 public function waitForElement($selector, $locator, $timeout = 10000) {
65 $page = $this->session->getPage();
67 $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
68 return $page->find($selector, $locator);
75 * Waits for the specified selector and returns it when available and visible.
77 * @param string $selector
78 * The selector engine name. See ElementInterface::findAll() for the
79 * supported selectors.
80 * @param string|array $locator
81 * The selector locator.
83 * (Optional) Timeout in milliseconds, defaults to 10000.
85 * @return \Behat\Mink\Element\NodeElement|null
86 * The page element node if found and visible, NULL if not.
88 * @see \Behat\Mink\Element\ElementInterface::findAll()
90 public function waitForElementVisible($selector, $locator, $timeout = 10000) {
91 $page = $this->session->getPage();
93 $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
94 $element = $page->find($selector, $locator);
95 if (!empty($element) && $element->isVisible()) {
104 * Waits for a button (input[type=submit|image|button|reset], button) with
105 * specified locator and returns it.
107 * @param string $locator
108 * The button ID, value or alt string.
109 * @param int $timeout
110 * (Optional) Timeout in milliseconds, defaults to 10000.
112 * @return \Behat\Mink\Element\NodeElement|null
113 * The page element node if found, NULL if not.
115 public function waitForButton($locator, $timeout = 10000) {
116 return $this->waitForElement('named', ['button', $locator], $timeout);
120 * Waits for a link with specified locator and returns it when available.
122 * @param string $locator
123 * The link ID, title, text or image alt.
124 * @param int $timeout
125 * (Optional) Timeout in milliseconds, defaults to 10000.
127 * @return \Behat\Mink\Element\NodeElement|null
128 * The page element node if found, NULL if not.
130 public function waitForLink($locator, $timeout = 10000) {
131 return $this->waitForElement('named', ['link', $locator], $timeout);
135 * Waits for a field with specified locator and returns it when available.
137 * @param string $locator
138 * The input ID, name or label for the field (input, textarea, select).
139 * @param int $timeout
140 * (Optional) Timeout in milliseconds, defaults to 10000.
142 * @return \Behat\Mink\Element\NodeElement|null
143 * The page element node if found, NULL if not.
145 public function waitForField($locator, $timeout = 10000) {
146 return $this->waitForElement('named', ['field', $locator], $timeout);
150 * Waits for an element by its id and returns it when available.
154 * @param int $timeout
155 * (Optional) Timeout in milliseconds, defaults to 10000.
157 * @return \Behat\Mink\Element\NodeElement|null
158 * The page element node if found, NULL if not.
160 public function waitForId($id, $timeout = 10000) {
161 return $this->waitForElement('named', ['id', $id], $timeout);
165 * Waits for the jQuery autocomplete delay duration.
167 * @see https://api.jqueryui.com/autocomplete/#option-delay
169 public function waitOnAutocomplete() {
170 // Wait for the autocomplete to be visible.
171 return $this->waitForElementVisible('css', '.ui-autocomplete li');
175 * Test that a node, or its specific corner, is visible in the viewport.
177 * Note: Always set the viewport size. This can be done with a PhantomJS
178 * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
179 * Drupal CI Javascript tests by default use a viewport of 1024x768px.
181 * @param string $selector_type
182 * The element selector type (CSS, XPath).
183 * @param string|array $selector
184 * The element selector. Note: the first found element is used.
185 * @param bool|string $corner
186 * (Optional) The corner to test:
187 * topLeft, topRight, bottomRight, bottomLeft.
188 * Or FALSE to check the complete element (default).
189 * @param string $message
190 * (optional) A message for the exception.
192 * @throws \Behat\Mink\Exception\ElementHtmlException
193 * When the element doesn't exist.
194 * @throws \Behat\Mink\Exception\ElementNotFoundException
195 * When the element is not visible in the viewport.
197 public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
198 $node = $this->session->getPage()->find($selector_type, $selector);
199 if ($node === NULL) {
200 if (is_array($selector)) {
201 $selector = implode(' ', $selector);
203 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
206 // Check if the node is visible on the page, which is a prerequisite of
207 // being visible in the viewport.
208 if (!$node->isVisible()) {
209 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
212 $result = $this->checkNodeVisibilityInViewport($node, $corner);
215 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
220 * Test that a node, or its specific corner, is not visible in the viewport.
222 * Note: the node should exist in the page, otherwise this assertion fails.
224 * @param string $selector_type
225 * The element selector type (CSS, XPath).
226 * @param string|array $selector
227 * The element selector. Note: the first found element is used.
228 * @param bool|string $corner
229 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
230 * Or FALSE to check the complete element (default).
231 * @param string $message
232 * (optional) A message for the exception.
234 * @throws \Behat\Mink\Exception\ElementHtmlException
235 * When the element doesn't exist.
236 * @throws \Behat\Mink\Exception\ElementNotFoundException
237 * When the element is not visible in the viewport.
239 * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
241 public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
242 $node = $this->session->getPage()->find($selector_type, $selector);
243 if ($node === NULL) {
244 if (is_array($selector)) {
245 $selector = implode(' ', $selector);
247 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
250 $result = $this->checkNodeVisibilityInViewport($node, $corner);
253 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
258 * Check the visibility of a node, or its specific corner.
260 * @param \Behat\Mink\Element\NodeElement $node
262 * @param bool|string $corner
263 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
264 * Or FALSE to check the complete element (default).
267 * Returns TRUE if the node is visible in the viewport, FALSE otherwise.
269 * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
270 * When an invalid corner specification is given.
272 private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
273 $xpath = $node->getXpath();
275 // Build the Javascript to test if the complete element or a specific corner
276 // is in the viewport.
279 $test_javascript_function = <<<JS
280 function t(r, lx, ly) {
292 $test_javascript_function = <<<JS
293 function t(r, lx, ly) {
305 $test_javascript_function = <<<JS
306 function t(r, lx, ly) {
318 $test_javascript_function = <<<JS
319 function t(r, lx, ly) {
331 $test_javascript_function = <<<JS
332 function t(r, lx, ly) {
343 // Throw an exception if an invalid corner parameter is given.
345 throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
348 // Build the full Javascript test. The shared logic gets the corner
349 // specific test logic injected.
350 $full_javascript_visibility_test = <<<JS
354 e = d.documentElement,
355 n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
356 r = n.getBoundingClientRect(),
357 lx = (w.innerWidth || e.clientWidth),
358 ly = (w.innerHeight || e.clientHeight);
361 }($test_javascript_function));
364 // Check the visibility by injecting and executing the full Javascript test
365 // script in the page.
366 return $this->session->evaluateScript($full_javascript_visibility_test);