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()) {
105 * Waits for a button (input[type=submit|image|button|reset], button) with
106 * specified locator and returns it.
108 * @param string $locator
109 * The button ID, value or alt string.
110 * @param int $timeout
111 * (Optional) Timeout in milliseconds, defaults to 10000.
113 * @return \Behat\Mink\Element\NodeElement|null
114 * The page element node if found, NULL if not.
116 public function waitForButton($locator, $timeout = 10000) {
117 return $this->waitForElement('named', ['button', $locator], $timeout);
121 * Waits for a link with specified locator and returns it when available.
123 * @param string $locator
124 * The link ID, title, text or image alt.
125 * @param int $timeout
126 * (Optional) Timeout in milliseconds, defaults to 10000.
128 * @return \Behat\Mink\Element\NodeElement|null
129 * The page element node if found, NULL if not.
131 public function waitForLink($locator, $timeout = 10000) {
132 return $this->waitForElement('named', ['link', $locator], $timeout);
136 * Waits for a field with specified locator and returns it when available.
138 * @param string $locator
139 * The input ID, name or label for the field (input, textarea, select).
140 * @param int $timeout
141 * (Optional) Timeout in milliseconds, defaults to 10000.
143 * @return \Behat\Mink\Element\NodeElement|null
144 * The page element node if found, NULL if not.
146 public function waitForField($locator, $timeout = 10000) {
147 return $this->waitForElement('named', ['field', $locator], $timeout);
151 * Waits for an element by its id and returns it when available.
155 * @param int $timeout
156 * (Optional) Timeout in milliseconds, defaults to 10000.
158 * @return \Behat\Mink\Element\NodeElement|null
159 * The page element node if found, NULL if not.
161 public function waitForId($id, $timeout = 10000) {
162 return $this->waitForElement('named', ['id', $id], $timeout);
166 * Waits for the jQuery autocomplete delay duration.
168 * @see https://api.jqueryui.com/autocomplete/#option-delay
170 public function waitOnAutocomplete() {
171 // Wait for the autocomplete to be visible.
172 return $this->waitForElementVisible('css', '.ui-autocomplete li');
176 * Test that a node, or its specific corner, is visible in the viewport.
178 * Note: Always set the viewport size. This can be done with a PhantomJS
179 * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
180 * Drupal CI Javascript tests by default use a viewport of 1024x768px.
182 * @param string $selector_type
183 * The element selector type (CSS, XPath).
184 * @param string|array $selector
185 * The element selector. Note: the first found element is used.
186 * @param bool|string $corner
187 * (Optional) The corner to test:
188 * topLeft, topRight, bottomRight, bottomLeft.
189 * Or FALSE to check the complete element (default).
190 * @param string $message
191 * (optional) A message for the exception.
193 * @throws \Behat\Mink\Exception\ElementHtmlException
194 * When the element doesn't exist.
195 * @throws \Behat\Mink\Exception\ElementNotFoundException
196 * When the element is not visible in the viewport.
198 public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
199 $node = $this->session->getPage()->find($selector_type, $selector);
200 if ($node === NULL) {
201 if (is_array($selector)) {
202 $selector = implode(' ', $selector);
204 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
207 // Check if the node is visible on the page, which is a prerequisite of
208 // being visible in the viewport.
209 if (!$node->isVisible()) {
210 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
213 $result = $this->checkNodeVisibilityInViewport($node, $corner);
216 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
221 * Test that a node, or its specific corner, is not visible in the viewport.
223 * Note: the node should exist in the page, otherwise this assertion fails.
225 * @param string $selector_type
226 * The element selector type (CSS, XPath).
227 * @param string|array $selector
228 * The element selector. Note: the first found element is used.
229 * @param bool|string $corner
230 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
231 * Or FALSE to check the complete element (default).
232 * @param string $message
233 * (optional) A message for the exception.
235 * @throws \Behat\Mink\Exception\ElementHtmlException
236 * When the element doesn't exist.
237 * @throws \Behat\Mink\Exception\ElementNotFoundException
238 * When the element is not visible in the viewport.
240 * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
242 public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
243 $node = $this->session->getPage()->find($selector_type, $selector);
244 if ($node === NULL) {
245 if (is_array($selector)) {
246 $selector = implode(' ', $selector);
248 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
251 $result = $this->checkNodeVisibilityInViewport($node, $corner);
254 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
259 * Check the visibility of a node, or its specific corner.
261 * @param \Behat\Mink\Element\NodeElement $node
263 * @param bool|string $corner
264 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
265 * Or FALSE to check the complete element (default).
268 * Returns TRUE if the node is visible in the viewport, FALSE otherwise.
270 * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
271 * When an invalid corner specification is given.
273 private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
274 $xpath = $node->getXpath();
276 // Build the Javascript to test if the complete element or a specific corner
277 // is in the viewport.
280 $test_javascript_function = <<<JS
281 function t(r, lx, ly) {
293 $test_javascript_function = <<<JS
294 function t(r, lx, ly) {
306 $test_javascript_function = <<<JS
307 function t(r, lx, ly) {
319 $test_javascript_function = <<<JS
320 function t(r, lx, ly) {
332 $test_javascript_function = <<<JS
333 function t(r, lx, ly) {
344 // Throw an exception if an invalid corner parameter is given.
346 throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
349 // Build the full Javascript test. The shared logic gets the corner
350 // specific test logic injected.
351 $full_javascript_visibility_test = <<<JS
355 e = d.documentElement,
356 n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
357 r = n.getBoundingClientRect(),
358 lx = (w.innerWidth || e.clientWidth),
359 ly = (w.innerHeight || e.clientHeight);
362 }($test_javascript_function));
365 // Check the visibility by injecting and executing the full Javascript test
366 // script in the page.
367 return $this->session->evaluateScript($full_javascript_visibility_test);