From c9fb29d5eaa83ca3540e765a40db36ad0343195c Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole Date: Tue, 28 Jun 2016 09:27:56 +0100 Subject: [PATCH] Issue #2750941 by dawehner, klausi, jibran: Additional BC assertions from WebTestBase to BrowserTestBase --- .../FunctionalTests/AssertLegacyTrait.php | 196 +++++++++++++++++- core/tests/Drupal/Tests/BrowserTestBase.php | 132 +++++------- core/tests/Drupal/Tests/WebAssert.php | 189 ++++++++++++++++- 3 files changed, 426 insertions(+), 91 deletions(-) diff --git a/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php b/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php index 0789fc804d7..ebd4850e562 100644 --- a/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php +++ b/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php @@ -124,7 +124,7 @@ trait AssertLegacyTrait { protected function assertFieldByName($name, $value = NULL) { $this->assertSession()->fieldExists($name); if ($value !== NULL) { - $this->assertSession()->fieldValueEquals($name, $value); + $this->assertSession()->fieldValueEquals($name, (string) $value); } } @@ -147,6 +147,32 @@ trait AssertLegacyTrait { $this->assertFieldByName($id, $value); } + /** + * Asserts that a field exists with the given name or ID. + * + * @param string $field + * Name or ID of field to assert. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldExists() instead. + */ + protected function assertField($field) { + $this->assertSession()->fieldExists($field); + } + + /** + * Asserts that a field exists with the given name or ID does NOT exist. + * + * @param string $field + * Name or ID of field to assert. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldNotExists() instead. + */ + protected function assertNoField($field) { + $this->assertSession()->fieldNotExists($field); + } + /** * Passes if the raw text IS found on the loaded page, fail otherwise. * @@ -162,13 +188,33 @@ trait AssertLegacyTrait { $this->assertSession()->responseContains($raw); } + /** + * Passes if the raw text IS not found on the loaded page, fail otherwise. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->responseNotContains() instead. + */ + protected function assertNoRaw($raw) { + $this->assertSession()->responseNotContains($raw); + } + /** * Pass if the page title is the given string. * * @param string $expected_title * The string the page title should be. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->titleEquals() instead. */ protected function assertTitle($expected_title) { + // Cast MarkupInterface to string. + $expected_title = (string) $expected_title; return $this->assertSession()->titleEquals($expected_title); } @@ -181,11 +227,92 @@ trait AssertLegacyTrait { * Text between the anchor tags. * @param int $index * Link position counting from zero. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkExists() instead. */ protected function assertLink($label, $index = 0) { return $this->assertSession()->linkExists($label, $index); } + /** + * Passes if a link with the specified label is not found. + * + * @param string|\Drupal\Component\Render\MarkupInterface $label + * Text between the anchor tags. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkNotExists() instead. + */ + protected function assertNoLink($label) { + return $this->assertSession()->linkNotExists($label); + } + + /** + * Passes if a link containing a given href (part) is found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param int $index + * Link position counting from zero. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkByHref() instead. + */ + protected function assertLinkByHref($href, $index = 0) { + $this->assertSession()->linkByHrefExists($href, $index); + } + + /** + * Passes if a link containing a given href (part) is not found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->linkByHrefNotExists() instead. + */ + protected function assertNoLinkByHref($href) { + $this->assertSession()->linkByHrefNotExists($href); + } + + /** + * Asserts that a field does not exist with the given ID and value. + * + * @param string $id + * ID of field to assert. + * @param string $value + * (optional) Value for the field, to assert that the field's value on the + * page doesn't match it. You may pass in NULL to skip checking the value, + * while still checking that the field doesn't exist. However, the default + * value ('') asserts that the field value is not an empty string. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldNotExists() or + * $this->assertSession()->fieldValueNotEquals() instead. + */ + protected function assertNoFieldById($id, $value = '') { + if ($this->getSession()->getPage()->findField($id)) { + $this->assertSession()->fieldValueNotEquals($id, (string) $value); + } + else { + $this->assertSession()->fieldNotExists($id); + } + } + + /** + * Passes if the internal browser's URL matches the given path. + * + * @param \Drupal\Core\Url|string $path + * The expected system path or URL. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->addressEquals() instead. + */ + protected function assertUrl($path) { + $this->assertSession()->addressEquals($path); + } + /** * Asserts that a select option in the current page exists. * @@ -217,20 +344,22 @@ trait AssertLegacyTrait { } /** - * Passes if the internal browser's URL matches the given path. + * Passes if the raw text IS found escaped on the loaded page, fail otherwise. * - * @param string $path - * The expected system path. + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. * * @deprecated Scheduled for removal in Drupal 9.0.0. - * Use $this->assertSession()->addressEquals() instead. + * Use $this->assertSession()->assertEscaped() instead. */ - protected function assertUrl($path) { - $this->assertSession()->addressEquals($path); + protected function assertEscaped($raw) { + $this->assertSession()->assertEscaped($raw); } /** - * Passes if the raw text IS NOT found escaped on the loaded page. + * Passes if the raw text is not found escaped on the loaded page. * * Raw text refers to the raw HTML that the page generated. * @@ -244,4 +373,55 @@ trait AssertLegacyTrait { $this->assertSession()->assertNoEscaped($raw); } + /** + * Asserts whether an expected cache tag was present in the last response. + * + * @param string $expected_cache_tag + * The expected cache tag. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->responseHeaderContains() instead. + */ + protected function assertCacheTag($expected_cache_tag) { + $this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', $expected_cache_tag); + } + + /** + * Returns WebAssert object. + * + * @param string $name + * (optional) Name of the session. Defaults to the active session. + * + * @return \Drupal\Tests\WebAssert + * A new web-assert option for asserting the presence of elements with. + */ + abstract public function assertSession($name = NULL); + + /** + * Builds an XPath query. + * + * Builds an XPath query by replacing placeholders in the query by the value + * of the arguments. + * + * XPath 1.0 (the version supported by libxml2, the underlying XML library + * used by PHP) doesn't support any form of quotation. This function + * simplifies the building of XPath expression. + * + * @param string $xpath + * An XPath query, possibly with placeholders in the form ':name'. + * @param array $args + * An array of arguments with keys in the form ':name' matching the + * placeholders in the query. The values may be either strings or numeric + * values. + * + * @return string + * An XPath query with arguments replaced. + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->buildXPathQuery() instead. + */ + public function buildXPathQuery($xpath, array $args = array()) { + return $this->assertSession()->buildXPathQuery($xpath, $args); + } + } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index 70a1840179d..3beb32f2f52 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -43,11 +43,14 @@ use Symfony\Component\HttpFoundation\Request; */ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { use AssertHelperTrait; - use BlockCreationTrait; + use BlockCreationTrait { + placeBlock as drupalPlaceBlock; + } use AssertLegacyTrait; use RandomGeneratorTrait; use SessionTestTrait; use NodeCreationTrait { + getNodeByTitle as drupalGetNodeByTitle; createNode as drupalCreateNode; } use ContentTypeCreationTrait { @@ -574,6 +577,46 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { $session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix)); } + /** + * Builds an a absolute URL from a system path or a URL object. + * + * @param string|\Drupal\Core\Url $path + * A system path or a URL. + * @param array $options + * Options to be passed to Url::fromUri(). + * + * @return string + * An absolute URL stsring. + */ + protected function buildUrl($path, array $options = array()) { + if ($path instanceof Url) { + $url_options = $path->getOptions(); + $options = $url_options + $options; + $path->setOptions($options); + return $path->setAbsolute()->toString(); + } + // The URL generator service is not necessarily available yet; e.g., in + // interactive installer tests. + elseif ($this->container->has('url_generator')) { + $force_internal = isset($options['external']) && $options['external'] == FALSE; + if (!$force_internal && UrlHelper::isExternal($path)) { + return Url::fromUri($path, $options)->toString(); + } + else { + $uri = $path === '' ? 'base:/' : 'base:/' . $path; + // Path processing is needed for language prefixing. Skip it when a + // path that may look like an external URL is being used as internal. + $options['path_processing'] = !$force_internal; + return Url::fromUri($uri, $options) + ->setAbsolute() + ->toString(); + } + } + else { + return $this->getAbsoluteUrl($path); + } + } + /** * Retrieves a Drupal path or an absolute path. * @@ -587,28 +630,8 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { */ protected function drupalGet($path, array $options = array()) { $options['absolute'] = TRUE; + $url = $this->buildUrl($path, $options); - if ($path instanceof Url) { - $url_options = $path->getOptions(); - $options = $url_options + $options; - $path->setOptions($options); - $url = $path->setAbsolute()->toString(); - } - // The URL generator service is not necessarily available yet; e.g., in - // interactive installer tests. - elseif ($this->container->has('url_generator')) { - if (UrlHelper::isExternal($path)) { - $url = Url::fromUri($path, $options)->toString(); - } - else { - // This is needed for language prefixing. - $options['path_processing'] = TRUE; - $url = Url::fromUri('base:/' . $path, $options)->toString(); - } - } - else { - $url = $this->getAbsoluteUrl($path); - } $session = $this->getSession(); $this->prepareRequest(); @@ -902,6 +925,16 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { // Edit the form values. foreach ($edit as $name => $value) { $field = $assert_session->fieldExists($name, $form); + + // Provide support for the values '1' and '0' for checkboxes instead of + // TRUE and FALSE. + // @todo Get rid of supporting 1/0 by converting all tests cases using + // this to boolean values. + $field_type = $field->getAttribute('type'); + if ($field_type === 'checkbox') { + $value = (bool) $value; + } + $field->setValue($value); } @@ -1690,63 +1723,10 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { * The list of elements matching the xpath expression. */ protected function xpath($xpath, array $arguments = []) { - $xpath = $this->buildXPathQuery($xpath, $arguments); + $xpath = $this->assertSession()->buildXPathQuery($xpath, $arguments); return $this->getSession()->getPage()->findAll('xpath', $xpath); } - /** - * Builds an XPath query. - * - * Builds an XPath query by replacing placeholders in the query by the value - * of the arguments. - * - * XPath 1.0 (the version supported by libxml2, the underlying XML library - * used by PHP) doesn't support any form of quotation. This function - * simplifies the building of XPath expression. - * - * @param string $xpath - * An XPath query, possibly with placeholders in the form ':name'. - * @param array $args - * An array of arguments with keys in the form ':name' matching the - * placeholders in the query. The values may be either strings or numeric - * values. - * - * @return string - * An XPath query with arguments replaced. - */ - protected function buildXPathQuery($xpath, array $args = array()) { - // Replace placeholders. - foreach ($args as $placeholder => $value) { - // Cast MarkupInterface objects to string. - if (is_object($value)) { - $value = (string) $value; - } - // XPath 1.0 doesn't support a way to escape single or double quotes in a - // string literal. We split double quotes out of the string, and encode - // them separately. - if (is_string($value)) { - // Explode the text at the quote characters. - $parts = explode('"', $value); - - // Quote the parts. - foreach ($parts as &$part) { - $part = '"' . $part . '"'; - } - - // Return the string. - $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0]; - } - - // Use preg_replace_callback() instead of preg_replace() to prevent the - // regular expression engine from trying to substitute backreferences. - $replacement = function ($matches) use ($value) { - return $value; - }; - $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath); - } - return $xpath; - } - /** * Configuration accessor for tests. Returns non-overridden configuration. * diff --git a/core/tests/Drupal/Tests/WebAssert.php b/core/tests/Drupal/Tests/WebAssert.php index ec31fdf95ee..6156dda8c80 100644 --- a/core/tests/Drupal/Tests/WebAssert.php +++ b/core/tests/Drupal/Tests/WebAssert.php @@ -8,6 +8,7 @@ use Behat\Mink\Element\TraversableElement; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Session; use Drupal\Component\Utility\Html; +use Drupal\Core\Url; /** * Defines a class with methods for asserting presence of elements during tests. @@ -38,6 +39,9 @@ class WebAssert extends MinkWebAssert { * {@inheritdoc} */ protected function cleanUrl($url) { + if ($url instanceof Url) { + $url = $url->setAbsolute()->toString(); + } // Strip the base URL from the beginning for absolute URLs. if ($this->baseUrl !== '' && strpos($url, $this->baseUrl) === 0) { $url = substr($url, strlen($this->baseUrl)); @@ -75,6 +79,24 @@ class WebAssert extends MinkWebAssert { return $node; } + /** + * Checks that the specific button does NOT exist on the current page. + * + * @param string $button + * One of id|name|label|value for the button. + * @param \Behat\Mink\Element\TraversableElement $container + * (optional) The document to check against. Defaults to the current page. + * + * @throws \Behat\Mink\Exception\ExpectationException + * When the button exists. + */ + public function buttonNotExists($button, TraversableElement $container = NULL) { + $container = $container ?: $this->session->getPage(); + $node = $container->findButton($button); + + $this->assert(NULL === $node, sprintf('A button "%s" appears on this page, but it should not.', $button)); + } + /** * Checks that specific select field exists on the current page. * @@ -191,7 +213,7 @@ class WebAssert extends MinkWebAssert { * * An optional link index may be passed. * - * @param string|\Drupal\Component\Render\MarkupInterface $label + * @param string $label * Text between the anchor tags. * @param int $index * Link position counting from zero. @@ -204,14 +226,126 @@ class WebAssert extends MinkWebAssert { * Thrown when element doesn't exist, or the link label is a different one. */ public function linkExists($label, $index = 0, $message = '') { - // Cast MarkupInterface objects to string. - $label = (string) $label; $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label])); $links = $this->session->getPage()->findAll('named', ['link', $label]); - if (empty($links[$index])) { - throw new ExpectationException($message); + $this->assert(!empty($links[$index]), $message); + } + + /** + * Passes if a link with the specified label is not found. + * + * An optional link index may be passed. + * + * @param string $label + * Text between the anchor tags. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use strtr() to embed variables in the message text, not + * t(). If left blank, a default message will be displayed. + * + * @throws \Behat\Mink\Exception\ExpectationException + * Thrown when element doesn't exist, or the link label is a different one. + */ + public function linkNotExists($label, $message = '') { + $message = ($message ? $message : strtr('Link with label %label found.', ['%label' => $label])); + $links = $this->session->getPage()->findAll('named', ['link', $label]); + $this->assert(empty($links), $message); + } + + /** + * Passes if a link containing a given href (part) is found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param int $index + * Link position counting from zero. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed + * variables in the message text, not t(). If left blank, a default message + * will be displayed. + * + * @throws \Behat\Mink\Exception\ExpectationException + * Thrown when element doesn't exist, or the link label is a different one. + */ + public function linkByHrefExists($href, $index = 0, $message = '') { + $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]); + $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href])); + $links = $this->session->getPage()->findAll('xpath', $xpath); + $this->assert(!empty($links[$index]), $message); + } + + /** + * Passes if a link containing a given href (part) is not found. + * + * @param string $href + * The full or partial value of the 'href' attribute of the anchor tag. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed + * variables in the message text, not t(). If left blank, a default message + * will be displayed. + * + * @throws \Behat\Mink\Exception\ExpectationException + * Thrown when element doesn't exist, or the link label is a different one. + */ + public function linkByHrefNotExists($href, $message = '') { + $xpath = $this->buildXPathQuery('//a[contains(@href, :href)]', [':href' => $href]); + $message = ($message ? $message : strtr('Link containing href %href found.', ['%href' => $href])); + $links = $this->session->getPage()->findAll('xpath', $xpath); + $this->assert(empty($links), $message); + } + + /** + * Builds an XPath query. + * + * Builds an XPath query by replacing placeholders in the query by the value + * of the arguments. + * + * XPath 1.0 (the version supported by libxml2, the underlying XML library + * used by PHP) doesn't support any form of quotation. This function + * simplifies the building of XPath expression. + * + * @param string $xpath + * An XPath query, possibly with placeholders in the form ':name'. + * @param array $args + * An array of arguments with keys in the form ':name' matching the + * placeholders in the query. The values may be either strings or numeric + * values. + * + * @return string + * An XPath query with arguments replaced. + */ + public function buildXPathQuery($xpath, array $args = array()) { + // Replace placeholders. + foreach ($args as $placeholder => $value) { + if (is_object($value)) { + throw new \InvalidArgumentException('Just pass in scalar values.'); + } + // XPath 1.0 doesn't support a way to escape single or double quotes in a + // string literal. We split double quotes out of the string, and encode + // them separately. + if (is_string($value)) { + // Explode the text at the quote characters. + $parts = explode('"', $value); + + // Quote the parts. + foreach ($parts as &$part) { + $part = '"' . $part . '"'; + } + + // Return the string. + $value = count($parts) > 1 ? 'concat(' . implode(', \'"\', ', $parts) . ')' : $parts[0]; + } + + // Use preg_replace_callback() instead of preg_replace() to prevent the + // regular expression engine from trying to substitute backreferences. + $replacement = function ($matches) use ($value) { + return $value; + }; + $xpath = preg_replace_callback('/' . preg_quote($placeholder) . '\b/', $replacement, $xpath); } - $this->assert($links[$index] !== NULL, $message); + return $xpath; } /** @@ -223,7 +357,19 @@ class WebAssert extends MinkWebAssert { * Raw (HTML) string to look for. */ public function assertNoEscaped($raw) { - $this->pageTextNotContains(Html::escape($raw)); + $this->responseNotContains(Html::escape($raw)); + } + + /** + * Passes if the raw text IS found escaped on the loaded page. + * + * Raw text refers to the raw HTML that the page generated. + * + * @param string $raw + * Raw (HTML) string to look for. + */ + public function assertEscaped($raw) { + $this->responseContains(Html::escape($raw)); } /** @@ -247,4 +393,33 @@ class WebAssert extends MinkWebAssert { throw new ExpectationException($message, $this->session->getDriver()); } + /** + * Checks that a given form field element is disabled. + * + * @param string $field + * One of id|name|label|value for the field. + * @param \Behat\Mink\Element\TraversableElement $container + * (optional) The document to check against. Defaults to the current page. + * + * @return \Behat\Mink\Element\NodeElement + * The matching element. + * + * @throws \Behat\Mink\Exception\ElementNotFoundException + * @throws \Behat\Mink\Exception\ExpectationException + */ + public function fieldDisabled($field, TraversableElement $container = NULL) { + $container = $container ?: $this->session->getPage(); + $node = $container->findField($field); + + if ($node === NULL) { + throw new ElementNotFoundException($this->session->getDriver(), 'field', 'id|name|label|value', $field); + } + + if (!$node->hasAttribute('disabled')) { + throw new ExpectationException("Field $field is disabled", $this->session->getDriver()); + } + + return $node; + } + }