diff --git a/browsertest-convert.php b/browsertest-convert.php new file mode 100644 index 0000000..65c0dbe --- /dev/null +++ b/browsertest-convert.php @@ -0,0 +1,617 @@ + 'Bad html ']; + } + } diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml index a2d5ed5..cbcf6d8 100644 --- a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml +++ b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml @@ -58,3 +58,11 @@ test_page_test.error: code: 200 requirements: _access: 'TRUE' + +test_page_test.encoded: + path: '/test-encoded' + defaults: + _title: 'Page with encoded HTML' + _controller: '\Drupal\test_page_test\Controller\Test::renderEncodedMarkup' + requirements: + _access: 'TRUE' diff --git a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php b/core/modules/system/tests/src/Functional/Bootstrap/DrupalSetMessageTest.php similarity index 90% rename from core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php rename to core/modules/system/tests/src/Functional/Bootstrap/DrupalSetMessageTest.php index 29ca02f..1b0f31a 100644 --- a/core/modules/system/src/Tests/Bootstrap/DrupalSetMessageTest.php +++ b/core/modules/system/tests/src/Functional/Bootstrap/DrupalSetMessageTest.php @@ -1,15 +1,15 @@ assertSession()->responseContains() instead. */ protected function assertText($text) { + // Cast MarkupInterface to string. + $text = (string) $text; + $content_type = $this->getSession()->getResponseHeader('Content-type'); // In case of a Non-HTML response (example: XML) check the original // response. @@ -64,7 +70,7 @@ protected function assertText($text) { $this->assertSession()->responseContains($text); } else { - $this->assertSession()->pageTextContains($text); + $this->assertTextHelper($text, FALSE); } } @@ -82,6 +88,9 @@ protected function assertText($text) { * $this->assertSession()->responseNotContains() instead. */ protected function assertNoText($text) { + // Cast MarkupInterface to string. + $text = (string) $text; + $content_type = $this->getSession()->getResponseHeader('Content-type'); // In case of a Non-HTML response (example: XML) check the original // response. @@ -89,11 +98,41 @@ protected function assertNoText($text) { $this->assertSession()->responseNotContains($text); } else { - $this->assertSession()->pageTextNotContains($text); + $this->assertTextHelper($text); } } /** + * Helper for assertText and assertNoText. + * + * @param string $text + * Plain text to look for. + * @param bool $not_exists + * (optional) TRUE if this text should not exist, FALSE if it should. + * Defaults to TRUE. + * + * @return bool + * TRUE on pass, FALSE on fail. + */ + protected function assertTextHelper($text, $not_exists = TRUE) { + $args = ['@text' => $text]; + $message = $not_exists ? new FormattableMarkup('"@text" not found', $args) : new FormattableMarkup('"@text" found', $args); + + $raw_content = $this->getSession()->getPage()->getContent(); + // Trying to simulate what the user sees, given that it removes all text + // inside the head tags, removes inline Javascript, fix all HTML entities, + // removes dangerous protocols and filtering out all HTML tags, as they are + // not visible in a normal browser. + $raw_content = preg_replace('@(.+?)@si', '', $raw_content); + $page_text = Xss::filter($raw_content, []); + + $actual = $not_exists == (strpos($page_text, (string) $text) === FALSE); + $this->assertTrue($actual, $message); + + return $actual; + } + + /** * Passes if the text is found ONLY ONCE on the text version of the page. * * The text version is the equivalent of what a user would see when viewing @@ -464,6 +503,103 @@ protected function assertNoFieldChecked($id) { } /** + * Asserts that a field exists in the current page by the given XPath. + * + * @param string $xpath + * XPath used to find the field. + * @param string $value + * (optional) Value of the field to assert. You may pass in NULL (default) + * to skip checking the actual value, while still checking that the field + * exists. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->xpath() instead and check the values directly in the test. + */ + protected function assertFieldByXPath($xpath, $value = NULL, $message = '') { + $fields = $this->xpath($xpath); + + $this->assertFieldsByValue($fields, $value, $message); + } + + /** + * Asserts that a field does not exist or its value does not match, by XPath. + * + * @param string $xpath + * XPath used to find the field. + * @param string $value + * (optional) Value of the field, to assert that the field's value on the + * page does not match it. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Use $this->assertSession()->fieldNotExists() or + * $this->assertSession()->fieldValueNotEquals() instead. + */ + protected function assertNoFieldByXPath($xpath, $value = NULL, $message = '') { + $fields = $this->xpath($xpath); + + // If value specified then check array for match. + $found = TRUE; + if (isset($value)) { + $found = FALSE; + if ($fields) { + foreach ($fields as $field) { + if ($field->getAttribute('value') == $value) { + $found = TRUE; + } + } + } + } + return $this->assertFalse($fields && $found, $message); + } + + /** + * Asserts that a field exists in the current page with a given Xpath result. + * + * @param \Behat\Mink\Element\NodeElement[] $fields + * Xml elements. + * @param string $value + * (optional) Value of the field to assert. You may pass in NULL (default) to skip + * checking the actual value, while still checking that the field exists. + * @param string $message + * (optional) A message to display with the assertion. Do not translate + * messages with t(). + * + * @deprecated Scheduled for removal in Drupal 9.0.0. + * Iterate over the fields yourself instead and directly check the values in + * the test. + */ + protected function assertFieldsByValue($fields, $value = NULL, $message = '') { + // If value specified then check array for match. + $found = TRUE; + if (isset($value)) { + $found = FALSE; + if ($fields) { + foreach ($fields as $field) { + if ($field->getAttribute('value') == $value) { + // Input element with correct value. + $found = TRUE; + } + elseif ($field->find('xpath', '//option[@value = ' . (new Escaper())->escapeLiteral($value) . ' and @selected = "selected"]')) { + // Select element with an option. + $found = TRUE; + } + elseif ($field->getText() == $value) { + // Text area with correct text. + $found = TRUE; + } + } + } + } + $this->assertTrue($fields && $found, $message); + } + + /** * Passes if the raw text IS found escaped on the loaded page, fail otherwise. * * Raw text refers to the raw HTML that the page generated. diff --git a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php index eb5ba4b..3d59b76 100644 --- a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php +++ b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php @@ -2,6 +2,7 @@ namespace Drupal\FunctionalTests; +use Drupal\Component\Utility\Html; use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; @@ -101,4 +102,34 @@ public function testError() { $this->drupalGet('test-error'); } + /** + * Tests that legacy assertions work. + */ + public function testAssertions() { + $account = $this->drupalCreateUser(['administer users'], 'test'); + $this->drupalLogin($account); + + $this->drupalGet('admin/people'); + $this->assertFieldByXpath('//table/tbody/tr[2]/td[1]/span', $account->getAccountName()); + + $this->drupalGet('user/' . $account->id() . '/edit'); + $this->assertFieldByXpath("//input[@id = 'edit-name']", $account->getAccountName()); + $this->assertFieldByXpath("//select[@id = 'edit-timezone--2']", 'Australia/Sydney'); + + $this->assertNoFieldByXPath('//notexisting'); + $this->assertNoFieldByXpath("//input[@id = 'edit-name']", 'wrong value'); + } + + /** + * Tests legacy asserts. + */ + public function testLegacyAsserts() { + $this->drupalGet('test-encoded'); + $this->assertSession()->statusCodeEquals(200); + $dangerous = 'Bad html '; + $sanitized = Html::escape($dangerous); + $this->assertNoText($dangerous); + $this->assertText($sanitized); + } + } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index b4a685e..226385f 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -21,6 +21,7 @@ use Drupal\Core\Session\UserSession; use Drupal\Core\Site\Settings; use Drupal\Core\StreamWrapper\StreamWrapperInterface; +use Drupal\Core\Test\AssertMailTrait; use Drupal\Core\Test\TestRunnerKernel; use Drupal\Core\Url; use Drupal\Core\Test\TestDatabase; @@ -63,6 +64,10 @@ createUser as drupalCreateUser; } + use AssertMailTrait { + getMails as drupalGetMails; + } + /** * Class loader. * @@ -932,7 +937,9 @@ protected function submitForm(array $edit, $submit, $form_html_id = NULL) { * @param array $options * Options to be forwarded to the url generator. */ - protected function drupalPostForm($path, array $edit, $submit, array $options = array()) { + protected function drupalPostForm($path, $edit, $submit, array $options = array()) { + $edit = (array) $edit; + if (is_object($submit)) { // Cast MarkupInterface objects to string. $submit = (string) $submit;