3 namespace Drupal\Tests;
5 use Behat\Mink\Exception\ExpectationException;
6 use Behat\Mink\WebAssert as MinkWebAssert;
7 use Behat\Mink\Element\TraversableElement;
8 use Behat\Mink\Exception\ElementNotFoundException;
9 use Behat\Mink\Session;
10 use Drupal\Component\Utility\Html;
14 * Defines a class with methods for asserting presence of elements during tests.
16 class WebAssert extends MinkWebAssert {
19 * The absolute URL of the site under test.
23 protected $baseUrl = '';
28 * @param \Behat\Mink\Session $session
29 * The Behat session object;
30 * @param string $base_url
31 * The base URL of the site under test.
33 public function __construct(Session $session, $base_url = '') {
34 parent::__construct($session);
35 $this->baseUrl = $base_url;
41 protected function cleanUrl($url) {
42 if ($url instanceof Url) {
43 $url = $url->setAbsolute()->toString();
45 // Strip the base URL from the beginning for absolute URLs.
46 if ($this->baseUrl !== '' && strpos($url, $this->baseUrl) === 0) {
47 $url = substr($url, strlen($this->baseUrl));
49 // Make sure there is a forward slash at the beginning of relative URLs for
51 if (parse_url($url, PHP_URL_HOST) === NULL && strpos($url, '/') !== 0) {
54 return parent::cleanUrl($url);
58 * Checks that specific button exists on the current page.
60 * @param string $button
61 * One of id|name|label|value for the button.
62 * @param \Behat\Mink\Element\TraversableElement $container
63 * (optional) The document to check against. Defaults to the current page.
65 * @return \Behat\Mink\Element\NodeElement
66 * The matching element.
68 * @throws \Behat\Mink\Exception\ElementNotFoundException
69 * When the element doesn't exist.
71 public function buttonExists($button, TraversableElement $container = NULL) {
72 $container = $container ?: $this->session->getPage();
73 $node = $container->findButton($button);
76 throw new ElementNotFoundException($this->session, 'button', 'id|name|label|value', $button);
83 * Checks that the specific button does NOT exist on the current page.
85 * @param string $button
86 * One of id|name|label|value for the button.
87 * @param \Behat\Mink\Element\TraversableElement $container
88 * (optional) The document to check against. Defaults to the current page.
90 * @throws \Behat\Mink\Exception\ExpectationException
91 * When the button exists.
93 public function buttonNotExists($button, TraversableElement $container = NULL) {
94 $container = $container ?: $this->session->getPage();
95 $node = $container->findButton($button);
97 $this->assert(NULL === $node, sprintf('A button "%s" appears on this page, but it should not.', $button));
101 * Checks that specific select field exists on the current page.
103 * @param string $select
104 * One of id|name|label|value for the select field.
105 * @param \Behat\Mink\Element\TraversableElement $container
106 * (optional) The document to check against. Defaults to the current page.
108 * @return \Behat\Mink\Element\NodeElement
109 * The matching element
111 * @throws \Behat\Mink\Exception\ElementNotFoundException
112 * When the element doesn't exist.
114 public function selectExists($select, TraversableElement $container = NULL) {
115 $container = $container ?: $this->session->getPage();
116 $node = $container->find('named', [
118 $this->session->getSelectorsHandler()->xpathLiteral($select),
121 if ($node === NULL) {
122 throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
129 * Checks that specific option in a select field exists on the current page.
131 * @param string $select
132 * One of id|name|label|value for the select field.
133 * @param string $option
135 * @param \Behat\Mink\Element\TraversableElement $container
136 * (optional) The document to check against. Defaults to the current page.
138 * @return \Behat\Mink\Element\NodeElement
139 * The matching option element
141 * @throws \Behat\Mink\Exception\ElementNotFoundException
142 * When the element doesn't exist.
144 public function optionExists($select, $option, TraversableElement $container = NULL) {
145 $container = $container ?: $this->session->getPage();
146 $select_field = $container->find('named', [
148 $this->session->getSelectorsHandler()->xpathLiteral($select),
151 if ($select_field === NULL) {
152 throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
155 $option_field = $select_field->find('named', ['option', $option]);
157 if ($option_field === NULL) {
158 throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $option);
161 return $option_field;
165 * Checks that an option in a select field does NOT exist on the current page.
167 * @param string $select
168 * One of id|name|label|value for the select field.
169 * @param string $option
170 * The option value that shoulkd not exist.
171 * @param \Behat\Mink\Element\TraversableElement $container
172 * (optional) The document to check against. Defaults to the current page.
174 * @throws \Behat\Mink\Exception\ElementNotFoundException
175 * When the select element doesn't exist.
177 public function optionNotExists($select, $option, TraversableElement $container = NULL) {
178 $container = $container ?: $this->session->getPage();
179 $select_field = $container->find('named', [
181 $this->session->getSelectorsHandler()->xpathLiteral($select),
184 if ($select_field === NULL) {
185 throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
188 $option_field = $select_field->find('named', ['option', $option]);
190 $this->assert($option_field === NULL, sprintf('An option "%s" exists in select "%s", but it should not.', $option, $select));
194 * Pass if the page title is the given string.
196 * @param string $expected_title
197 * The string the page title should be.
199 * @throws \Behat\Mink\Exception\ExpectationException
200 * Thrown when element doesn't exist, or the title is a different one.
202 public function titleEquals($expected_title) {
203 $title_element = $this->session->getPage()->find('css', 'title');
204 if (!$title_element) {
205 throw new ExpectationException('No title element found on the page', $this->session);
207 $actual_title = $title_element->getText();
208 $this->assert($expected_title === $actual_title, 'Title found');
212 * Passes if a link with the specified label is found.
214 * An optional link index may be passed.
216 * @param string $label
217 * Text between the anchor tags.
219 * Link position counting from zero.
220 * @param string $message
221 * (optional) A message to display with the assertion. Do not translate
222 * messages: use strtr() to embed variables in the message text, not
223 * t(). If left blank, a default message will be displayed.
225 * @throws \Behat\Mink\Exception\ExpectationException
226 * Thrown when element doesn't exist, or the link label is a different one.
228 public function linkExists($label, $index = 0, $message = '') {
229 $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label]));
230 $links = $this->session->getPage()->findAll('named', ['link', $label]);
231 $this->assert(!empty($links[$index]), $message);
235 * Passes if a link with the exactly specified label is found.
237 * An optional link index may be passed.
239 * @param string $label
240 * Text between the anchor tags.
242 * Link position counting from zero.
243 * @param string $message
244 * (optional) A message to display with the assertion. Do not translate
245 * messages: use strtr() to embed variables in the message text, not
246 * t(). If left blank, a default message will be displayed.
248 * @throws \Behat\Mink\Exception\ExpectationException
249 * Thrown when element doesn't exist, or the link label is a different one.
251 public function linkExistsExact($label, $index = 0, $message = '') {
252 $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label]));
253 $links = $this->session->getPage()->findAll('named_exact', ['link', $label]);
254 $this->assert(!empty($links[$index]), $message);
258 * Passes if a link with the specified label is not found.
260 * An optional link index may be passed.
262 * @param string $label
263 * Text between the anchor tags.
264 * @param string $message
265 * (optional) A message to display with the assertion. Do not translate
266 * messages: use strtr() to embed variables in the message text, not
267 * t(). If left blank, a default message will be displayed.
269 * @throws \Behat\Mink\Exception\ExpectationException
270 * Thrown when element doesn't exist, or the link label is a different one.
272 public function linkNotExists($label, $message = '') {
273 $message = ($message ? $message : strtr('Link with label %label not found.', ['%label' => $label]));
274 $links = $this->session->getPage()->findAll('named', ['link', $label]);
275 $this->assert(empty($links), $message);
279 * Passes if a link with the exactly specified label is not found.
281 * An optional link index may be passed.
283 * @param string $label
284 * Text between the anchor tags.
285 * @param string $message
286 * (optional) A message to display with the assertion. Do not translate
287 * messages: use strtr() to embed variables in the message text, not
288 * t(). If left blank, a default message will be displayed.
290 * @throws \Behat\Mink\Exception\ExpectationException
291 * Thrown when element doesn't exist, or the link label is a different one.
293 public function linkNotExistsExact($label, $message = '') {
294 $message = ($message ? $message : strtr('Link with label %label not found.', ['%label' => $label]));
295 $links = $this->session->getPage()->findAll('named_exact', ['link', $label]);
296 $this->assert(empty($links), $message);
300 * Passes if a link containing a given href (part) is found.
302 * @param string $href
303 * The full or partial value of the 'href' attribute of the anchor tag.
305 * Link position counting from zero.
306 * @param string $message
307 * (optional) A message to display with the assertion. Do not translate
308 * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
309 * variables in the message text, not t(). If left blank, a default message
312 * @throws \Behat\Mink\Exception\ExpectationException
313 * Thrown when element doesn't exist, or the link label is a different one.
315 public function linkByHrefExists($href, $index = 0, $message = '') {
316 $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
317 $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href]));
318 $links = $this->session->getPage()->findAll('xpath', $xpath);
319 $this->assert(!empty($links[$index]), $message);
323 * Passes if a link containing a given href (part) is not found.
325 * @param string $href
326 * The full or partial value of the 'href' attribute of the anchor tag.
327 * @param string $message
328 * (optional) A message to display with the assertion. Do not translate
329 * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
330 * variables in the message text, not t(). If left blank, a default message
333 * @throws \Behat\Mink\Exception\ExpectationException
334 * Thrown when element doesn't exist, or the link label is a different one.
336 public function linkByHrefNotExists($href, $message = '') {
337 $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
338 $message = ($message ? $message : strtr('No link containing href %href found.', ['%href' => $href]));
339 $links = $this->session->getPage()->findAll('xpath', $xpath);
340 $this->assert(empty($links), $message);
344 * Builds an XPath query.
346 * Builds an XPath query by replacing placeholders in the query by the value
349 * XPath 1.0 (the version supported by libxml2, the underlying XML library
350 * used by PHP) doesn't support any form of quotation. This function
351 * simplifies the building of XPath expression.
353 * @param string $xpath
354 * An XPath query, possibly with placeholders in the form ':name'.
356 * An array of arguments with keys in the form ':name' matching the
357 * placeholders in the query. The values may be either strings or numeric
361 * An XPath query with arguments replaced.
363 public function buildXPathQuery($xpath, array $args = []) {
364 // Replace placeholders.
365 foreach ($args as $placeholder => $value) {
366 if (is_object($value)) {
367 throw new \InvalidArgumentException('Just pass in scalar values for $args and remove all t() calls from your test.');
369 // XPath 1.0 doesn't support a way to escape single or double quotes in a
370 // string literal. We split double quotes out of the string, and encode
372 if (is_string($value)) {
373 // Explode the text at the quote characters.
374 $parts = explode('"', $value);
377 foreach ($parts as &$part) {
378 $part = '"' . $part . '"';
381 // Return the string.
382 $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0];
385 // Use preg_replace_callback() instead of preg_replace() to prevent the
386 // regular expression engine from trying to substitute backreferences.
387 $replacement = function ($matches) use ($value) {
390 $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath);
396 * Passes if the raw text IS NOT found escaped on the loaded page.
398 * Raw text refers to the raw HTML that the page generated.
401 * Raw (HTML) string to look for.
403 public function assertNoEscaped($raw) {
404 $this->responseNotContains(Html::escape($raw));
408 * Passes if the raw text IS found escaped on the loaded page.
410 * Raw text refers to the raw HTML that the page generated.
413 * Raw (HTML) string to look for.
415 public function assertEscaped($raw) {
416 $this->responseContains(Html::escape($raw));
420 * Asserts a condition.
422 * The parent method is overridden because it is a private method.
424 * @param bool $condition
426 * @param string $message
427 * The success message.
429 * @throws \Behat\Mink\Exception\ExpectationException
430 * When the condition is not fulfilled.
432 public function assert($condition, $message) {
437 throw new ExpectationException($message, $this->session->getDriver());
441 * Checks that a given form field element is disabled.
443 * @param string $field
444 * One of id|name|label|value for the field.
445 * @param \Behat\Mink\Element\TraversableElement $container
446 * (optional) The document to check against. Defaults to the current page.
448 * @return \Behat\Mink\Element\NodeElement
449 * The matching element.
451 * @throws \Behat\Mink\Exception\ElementNotFoundException
452 * @throws \Behat\Mink\Exception\ExpectationException
454 public function fieldDisabled($field, TraversableElement $container = NULL) {
455 $container = $container ?: $this->session->getPage();
456 $node = $container->findField($field);
458 if ($node === NULL) {
459 throw new ElementNotFoundException($this->session->getDriver(), 'field', 'id|name|label|value', $field);
462 if (!$node->hasAttribute('disabled')) {
463 throw new ExpectationException("Field $field is disabled", $this->session->getDriver());
470 * Checks that specific hidden field exists.
472 * @param string $field
473 * One of id|name|value for the hidden field.
474 * @param \Behat\Mink\Element\TraversableElement $container
475 * (optional) The document to check against. Defaults to the current page.
477 * @return \Behat\Mink\Element\NodeElement
478 * The matching element.
480 * @throws \Behat\Mink\Exception\ElementNotFoundException
482 public function hiddenFieldExists($field, TraversableElement $container = NULL) {
483 $container = $container ?: $this->session->getPage();
484 if ($node = $container->find('hidden_field_selector', ['hidden_field', $field])) {
487 throw new ElementNotFoundException($this->session->getDriver(), 'form hidden field', 'id|name|value', $field);
491 * Checks that specific hidden field does not exists.
493 * @param string $field
494 * One of id|name|value for the hidden field.
495 * @param \Behat\Mink\Element\TraversableElement $container
496 * (optional) The document to check against. Defaults to the current page.
498 * @throws \Behat\Mink\Exception\ExpectationException
500 public function hiddenFieldNotExists($field, TraversableElement $container = NULL) {
501 $container = $container ?: $this->session->getPage();
502 $node = $container->find('hidden_field_selector', ['hidden_field', $field]);
503 $this->assert($node === NULL, "A hidden field '$field' exists on this page, but it should not.");
507 * Checks that specific hidden field have provided value.
509 * @param string $field
510 * One of id|name|value for the hidden field.
511 * @param string $value
512 * The hidden field value that needs to be checked.
513 * @param \Behat\Mink\Element\TraversableElement $container
514 * (optional) The document to check against. Defaults to the current page.
516 * @throws \Behat\Mink\Exception\ElementNotFoundException
517 * @throws \Behat\Mink\Exception\ExpectationException
519 public function hiddenFieldValueEquals($field, $value, TraversableElement $container = NULL) {
520 $node = $this->hiddenFieldExists($field, $container);
521 $actual = $node->getValue();
522 $regex = '/^' . preg_quote($value, '/') . '$/ui';
523 $message = "The hidden field '$field' value is '$actual', but '$value' expected.";
524 $this->assert((bool) preg_match($regex, $actual), $message);
528 * Checks that specific hidden field doesn't have the provided value.
530 * @param string $field
531 * One of id|name|value for the hidden field.
532 * @param string $value
533 * The hidden field value that needs to be checked.
534 * @param \Behat\Mink\Element\TraversableElement $container
535 * (optional) The document to check against. Defaults to the current page.
537 * @throws \Behat\Mink\Exception\ElementNotFoundException
538 * @throws \Behat\Mink\Exception\ExpectationException
540 public function hiddenFieldValueNotEquals($field, $value, TraversableElement $container = NULL) {
541 $node = $this->hiddenFieldExists($field, $container);
542 $actual = $node->getValue();
543 $regex = '/^' . preg_quote($value, '/') . '$/ui';
544 $message = "The hidden field '$field' value is '$actual', but it should not be.";
545 $this->assert(!preg_match($regex, $actual), $message);