comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
1 <?php
2
3 namespace Drupal\FunctionalJavascriptTests;
4
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;
10
11 /**
12 * Defines a class with methods for asserting presence of elements during tests.
13 */
14 class JSWebAssert extends WebAssert {
15
16 /**
17 * Waits for AJAX request to be completed.
18 *
19 * @param int $timeout
20 * (Optional) Timeout in milliseconds, defaults to 10000.
21 * @param string $message
22 * (optional) A message for exception.
23 *
24 * @throws \RuntimeException
25 * When the request is not completed. If left blank, a default message will
26 * be displayed.
27 */
28 public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {
29 $condition = <<<JS
30 (function() {
31 function isAjaxing(instance) {
32 return instance && instance.ajaxing === true;
33 }
34 return (
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))
39 );
40 }());
41 JS;
42 $result = $this->session->wait($timeout, $condition);
43 if (!$result) {
44 throw new \RuntimeException($message);
45 }
46 }
47
48 /**
49 * Waits for the specified selector and returns it when available.
50 *
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.
56 * @param int $timeout
57 * (Optional) Timeout in milliseconds, defaults to 10000.
58 *
59 * @return \Behat\Mink\Element\NodeElement|null
60 * The page element node if found, NULL if not.
61 *
62 * @see \Behat\Mink\Element\ElementInterface::findAll()
63 */
64 public function waitForElement($selector, $locator, $timeout = 10000) {
65 $page = $this->session->getPage();
66
67 $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
68 return $page->find($selector, $locator);
69 });
70
71 return $result;
72 }
73
74 /**
75 * Waits for the specified selector and returns it when available and visible.
76 *
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.
82 * @param int $timeout
83 * (Optional) Timeout in milliseconds, defaults to 10000.
84 *
85 * @return \Behat\Mink\Element\NodeElement|null
86 * The page element node if found and visible, NULL if not.
87 *
88 * @see \Behat\Mink\Element\ElementInterface::findAll()
89 */
90 public function waitForElementVisible($selector, $locator, $timeout = 10000) {
91 $page = $this->session->getPage();
92
93 $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {
94 $element = $page->find($selector, $locator);
95 if (!empty($element) && $element->isVisible()) {
96 return $element;
97 }
98 return NULL;
99 });
100
101 return $result;
102 }
103 /**
104 * Waits for a button (input[type=submit|image|button|reset], button) with
105 * specified locator and returns it.
106 *
107 * @param string $locator
108 * The button ID, value or alt string.
109 * @param int $timeout
110 * (Optional) Timeout in milliseconds, defaults to 10000.
111 *
112 * @return \Behat\Mink\Element\NodeElement|null
113 * The page element node if found, NULL if not.
114 */
115 public function waitForButton($locator, $timeout = 10000) {
116 return $this->waitForElement('named', ['button', $locator], $timeout);
117 }
118
119 /**
120 * Waits for a link with specified locator and returns it when available.
121 *
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.
126 *
127 * @return \Behat\Mink\Element\NodeElement|null
128 * The page element node if found, NULL if not.
129 */
130 public function waitForLink($locator, $timeout = 10000) {
131 return $this->waitForElement('named', ['link', $locator], $timeout);
132 }
133
134 /**
135 * Waits for a field with specified locator and returns it when available.
136 *
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.
141 *
142 * @return \Behat\Mink\Element\NodeElement|null
143 * The page element node if found, NULL if not.
144 */
145 public function waitForField($locator, $timeout = 10000) {
146 return $this->waitForElement('named', ['field', $locator], $timeout);
147 }
148
149 /**
150 * Waits for an element by its id and returns it when available.
151 *
152 * @param string $id
153 * The element ID.
154 * @param int $timeout
155 * (Optional) Timeout in milliseconds, defaults to 10000.
156 *
157 * @return \Behat\Mink\Element\NodeElement|null
158 * The page element node if found, NULL if not.
159 */
160 public function waitForId($id, $timeout = 10000) {
161 return $this->waitForElement('named', ['id', $id], $timeout);
162 }
163
164 /**
165 * Waits for the jQuery autocomplete delay duration.
166 *
167 * @see https://api.jqueryui.com/autocomplete/#option-delay
168 */
169 public function waitOnAutocomplete() {
170 // Wait for the autocomplete to be visible.
171 return $this->waitForElementVisible('css', '.ui-autocomplete li');
172 }
173
174 /**
175 * Test that a node, or it's specific corner, is visible in the viewport.
176 *
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.
180 *
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.
191 *
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.
196 */
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);
202 }
203 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
204 }
205
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);
210 }
211
212 $result = $this->checkNodeVisibilityInViewport($node, $corner);
213
214 if (!$result) {
215 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
216 }
217 }
218
219 /**
220 * Test that a node, or its specific corner, is not visible in the viewport.
221 *
222 * Note: the node should exist in the page, otherwise this assertion fails.
223 *
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.
233 *
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.
238 *
239 * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
240 */
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);
246 }
247 throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
248 }
249
250 $result = $this->checkNodeVisibilityInViewport($node, $corner);
251
252 if ($result) {
253 throw new ElementHtmlException($message, $this->session->getDriver(), $node);
254 }
255 }
256
257 /**
258 * Check the visibility of a node, or it's specific corner.
259 *
260 * @param \Behat\Mink\Element\NodeElement $node
261 * A valid node.
262 * @param bool|string $corner
263 * (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
264 * Or FALSE to check the complete element (default).
265 *
266 * @return bool
267 * Returns TRUE if the node is visible in the viewport, FALSE otherwise.
268 *
269 * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
270 * When an invalid corner specification is given.
271 */
272 private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
273 $xpath = $node->getXpath();
274
275 // Build the Javascript to test if the complete element or a specific corner
276 // is in the viewport.
277 switch ($corner) {
278 case 'topLeft':
279 $test_javascript_function = <<<JS
280 function t(r, lx, ly) {
281 return (
282 r.top >= 0 &&
283 r.top <= ly &&
284 r.left >= 0 &&
285 r.left <= lx
286 )
287 }
288 JS;
289 break;
290
291 case 'topRight':
292 $test_javascript_function = <<<JS
293 function t(r, lx, ly) {
294 return (
295 r.top >= 0 &&
296 r.top <= ly &&
297 r.right >= 0 &&
298 r.right <= lx
299 );
300 }
301 JS;
302 break;
303
304 case 'bottomRight':
305 $test_javascript_function = <<<JS
306 function t(r, lx, ly) {
307 return (
308 r.bottom >= 0 &&
309 r.bottom <= ly &&
310 r.right >= 0 &&
311 r.right <= lx
312 );
313 }
314 JS;
315 break;
316
317 case 'bottomLeft':
318 $test_javascript_function = <<<JS
319 function t(r, lx, ly) {
320 return (
321 r.bottom >= 0 &&
322 r.bottom <= ly &&
323 r.left >= 0 &&
324 r.left <= lx
325 );
326 }
327 JS;
328 break;
329
330 case FALSE:
331 $test_javascript_function = <<<JS
332 function t(r, lx, ly) {
333 return (
334 r.top >= 0 &&
335 r.left >= 0 &&
336 r.bottom <= ly &&
337 r.right <= lx
338 );
339 }
340 JS;
341 break;
342
343 // Throw an exception if an invalid corner parameter is given.
344 default:
345 throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
346 }
347
348 // Build the full Javascript test. The shared logic gets the corner
349 // specific test logic injected.
350 $full_javascript_visibility_test = <<<JS
351 (function(t){
352 var w = window,
353 d = document,
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);
359
360 return t(r, lx, ly);
361 }($test_javascript_function));
362 JS;
363
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);
367 }
368
369 }