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 specified label is not found.
237 * An optional link index may be passed.
239 * @param string $label
240 * Text between the anchor tags.
241 * @param string $message
242 * (optional) A message to display with the assertion. Do not translate
243 * messages: use strtr() to embed variables in the message text, not
244 * t(). If left blank, a default message will be displayed.
246 * @throws \Behat\Mink\Exception\ExpectationException
247 * Thrown when element doesn't exist, or the link label is a different one.
249 public function linkNotExists($label, $message = '') {
250 $message = ($message ? $message : strtr('Link with label %label not found.', ['%label' => $label]));
251 $links = $this->session->getPage()->findAll('named', ['link', $label]);
252 $this->assert(empty($links), $message);
256 * Passes if a link containing a given href (part) is found.
258 * @param string $href
259 * The full or partial value of the 'href' attribute of the anchor tag.
261 * Link position counting from zero.
262 * @param string $message
263 * (optional) A message to display with the assertion. Do not translate
264 * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
265 * variables in the message text, not t(). If left blank, a default message
268 * @throws \Behat\Mink\Exception\ExpectationException
269 * Thrown when element doesn't exist, or the link label is a different one.
271 public function linkByHrefExists($href, $index = 0, $message = '') {
272 $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
273 $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href]));
274 $links = $this->session->getPage()->findAll('xpath', $xpath);
275 $this->assert(!empty($links[$index]), $message);
279 * Passes if a link containing a given href (part) is not found.
281 * @param string $href
282 * The full or partial value of the 'href' attribute of the anchor tag.
283 * @param string $message
284 * (optional) A message to display with the assertion. Do not translate
285 * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
286 * variables in the message text, not t(). If left blank, a default message
289 * @throws \Behat\Mink\Exception\ExpectationException
290 * Thrown when element doesn't exist, or the link label is a different one.
292 public function linkByHrefNotExists($href, $message = '') {
293 $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]);
294 $message = ($message ? $message : strtr('No link containing href %href found.', ['%href' => $href]));
295 $links = $this->session->getPage()->findAll('xpath', $xpath);
296 $this->assert(empty($links), $message);
300 * Builds an XPath query.
302 * Builds an XPath query by replacing placeholders in the query by the value
305 * XPath 1.0 (the version supported by libxml2, the underlying XML library
306 * used by PHP) doesn't support any form of quotation. This function
307 * simplifies the building of XPath expression.
309 * @param string $xpath
310 * An XPath query, possibly with placeholders in the form ':name'.
312 * An array of arguments with keys in the form ':name' matching the
313 * placeholders in the query. The values may be either strings or numeric
317 * An XPath query with arguments replaced.
319 public function buildXPathQuery($xpath, array $args = []) {
320 // Replace placeholders.
321 foreach ($args as $placeholder => $value) {
322 if (is_object($value)) {
323 throw new \InvalidArgumentException('Just pass in scalar values for $args and remove all t() calls from your test.');
325 // XPath 1.0 doesn't support a way to escape single or double quotes in a
326 // string literal. We split double quotes out of the string, and encode
328 if (is_string($value)) {
329 // Explode the text at the quote characters.
330 $parts = explode('"', $value);
333 foreach ($parts as &$part) {
334 $part = '"' . $part . '"';
337 // Return the string.
338 $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0];
341 // Use preg_replace_callback() instead of preg_replace() to prevent the
342 // regular expression engine from trying to substitute backreferences.
343 $replacement = function ($matches) use ($value) {
346 $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath);
352 * Passes if the raw text IS NOT found escaped on the loaded page.
354 * Raw text refers to the raw HTML that the page generated.
357 * Raw (HTML) string to look for.
359 public function assertNoEscaped($raw) {
360 $this->responseNotContains(Html::escape($raw));
364 * Passes if the raw text IS found escaped on the loaded page.
366 * Raw text refers to the raw HTML that the page generated.
369 * Raw (HTML) string to look for.
371 public function assertEscaped($raw) {
372 $this->responseContains(Html::escape($raw));
376 * Asserts a condition.
378 * The parent method is overridden because it is a private method.
380 * @param bool $condition
382 * @param string $message
383 * The success message.
385 * @throws \Behat\Mink\Exception\ExpectationException
386 * When the condition is not fulfilled.
388 public function assert($condition, $message) {
393 throw new ExpectationException($message, $this->session->getDriver());
397 * Checks that a given form field element is disabled.
399 * @param string $field
400 * One of id|name|label|value for the field.
401 * @param \Behat\Mink\Element\TraversableElement $container
402 * (optional) The document to check against. Defaults to the current page.
404 * @return \Behat\Mink\Element\NodeElement
405 * The matching element.
407 * @throws \Behat\Mink\Exception\ElementNotFoundException
408 * @throws \Behat\Mink\Exception\ExpectationException
410 public function fieldDisabled($field, TraversableElement $container = NULL) {
411 $container = $container ?: $this->session->getPage();
412 $node = $container->findField($field);
414 if ($node === NULL) {
415 throw new ElementNotFoundException($this->session->getDriver(), 'field', 'id|name|label|value', $field);
418 if (!$node->hasAttribute('disabled')) {
419 throw new ExpectationException("Field $field is disabled", $this->session->getDriver());
426 * Checks that specific hidden field exists.
428 * @param string $field
429 * One of id|name|value for the hidden field.
430 * @param \Behat\Mink\Element\TraversableElement $container
431 * (optional) The document to check against. Defaults to the current page.
433 * @return \Behat\Mink\Element\NodeElement
434 * The matching element.
436 * @throws \Behat\Mink\Exception\ElementNotFoundException
438 public function hiddenFieldExists($field, TraversableElement $container = NULL) {
439 $container = $container ?: $this->session->getPage();
440 if ($node = $container->find('hidden_field_selector', ['hidden_field', $field])) {
443 throw new ElementNotFoundException($this->session->getDriver(), 'form hidden field', 'id|name|value', $field);
447 * Checks that specific hidden field does not exists.
449 * @param string $field
450 * One of id|name|value for the hidden field.
451 * @param \Behat\Mink\Element\TraversableElement $container
452 * (optional) The document to check against. Defaults to the current page.
454 * @throws \Behat\Mink\Exception\ExpectationException
456 public function hiddenFieldNotExists($field, TraversableElement $container = NULL) {
457 $container = $container ?: $this->session->getPage();
458 $node = $container->find('hidden_field_selector', ['hidden_field', $field]);
459 $this->assert($node === NULL, "A hidden field '$field' exists on this page, but it should not.");
463 * Checks that specific hidden field have provided value.
465 * @param string $field
466 * One of id|name|value for the hidden field.
467 * @param string $value
468 * The hidden field value that needs to be checked.
469 * @param \Behat\Mink\Element\TraversableElement $container
470 * (optional) The document to check against. Defaults to the current page.
472 * @throws \Behat\Mink\Exception\ElementNotFoundException
473 * @throws \Behat\Mink\Exception\ExpectationException
475 public function hiddenFieldValueEquals($field, $value, TraversableElement $container = NULL) {
476 $node = $this->hiddenFieldExists($field, $container);
477 $actual = $node->getValue();
478 $regex = '/^' . preg_quote($value, '/') . '$/ui';
479 $message = "The hidden field '$field' value is '$actual', but '$value' expected.";
480 $this->assert((bool) preg_match($regex, $actual), $message);
484 * Checks that specific hidden field doesn't have the provided value.
486 * @param string $field
487 * One of id|name|value for the hidden field.
488 * @param string $value
489 * The hidden field value that needs to be checked.
490 * @param \Behat\Mink\Element\TraversableElement $container
491 * (optional) The document to check against. Defaults to the current page.
493 * @throws \Behat\Mink\Exception\ElementNotFoundException
494 * @throws \Behat\Mink\Exception\ExpectationException
496 public function hiddenFieldValueNotEquals($field, $value, TraversableElement $container = NULL) {
497 $node = $this->hiddenFieldExists($field, $container);
498 $actual = $node->getValue();
499 $regex = '/^' . preg_quote($value, '/') . '$/ui';
500 $message = "The hidden field '$field' value is '$actual', but it should not be.";
501 $this->assert(!preg_match($regex, $actual), $message);