diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 2a32a9a..29bf205 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -620,9 +620,9 @@ function drupal_valid_test_ua($new_prefix = NULL) { // string. $http_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : NULL; $user_agent = isset($_COOKIE['SIMPLETEST_USER_AGENT']) ? $_COOKIE['SIMPLETEST_USER_AGENT'] : $http_user_agent; - if (isset($user_agent) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $user_agent, $matches)) { + if (isset($user_agent) && preg_match("/^(simpletest\d+):(.+):(.+):(.+)$/", $user_agent, $matches)) { list(, $prefix, $time, $salt, $hmac) = $matches; - $check_string = $prefix . ';' . $time . ';' . $salt; + $check_string = $prefix . ':' . $time . ':' . $salt; // Read the hash salt prepared by drupal_generate_test_ua(). // This function is called before settings.php is read and Drupal's error // handlers are set up. While Drupal's error handling may be properly @@ -679,8 +679,8 @@ function drupal_generate_test_ua($prefix) { } // Generate a moderately secure HMAC based on the database credentials. $salt = uniqid('', TRUE); - $check_string = $prefix . ';' . time() . ';' . $salt; - return $check_string . ';' . Crypt::hmacBase64($check_string, $key); + $check_string = $prefix . ':' . time() . ':' . $salt; + return $check_string . ':' . Crypt::hmacBase64($check_string, $key); } /** diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php index dc933ac..049db12 100644 --- a/core/modules/simpletest/src/BrowserTestBase.php +++ b/core/modules/simpletest/src/BrowserTestBase.php @@ -27,6 +27,7 @@ use Drupal\user\Entity\Role; use Drupal\user\Entity\User; use Drupal\user\UserInterface; +use Symfony\Component\CssSelector\CssSelector; use Symfony\Component\HttpFoundation\Request; /** @@ -226,7 +227,17 @@ protected $preserveGlobalState = FALSE; /** + * The base URL. + * + * @var string + */ + protected $baseUrl; + + /** * Initializes Mink sessions. + * + * @return \Behat\Mink\Session + * The mink session. */ protected function initMink() { $driver = $this->getDefaultDriverInstance(); @@ -241,6 +252,17 @@ protected function initMink() { $this->mink->setDefaultSessionName('default'); $this->registerSessions(); + // According to the W3C WebDriver specification a cookie can only be set if + // the cookie domain is equal to the domain of the active document. When the + // browser starts up the active document is not our domain but 'about:blank' + // or similar. To be able to set our User-Agent and Xdebug cookies at the + // start of the test we now do a request to the front page so the active + // document matches the domain. + // @see https://w3c.github.io/webdriver/webdriver-spec.html#add-cookie + // @see https://www.w3.org/Bugs/Public/show_bug.cgi?id=20975 + $session = $this->getSession(); + $session->visit($this->baseUrl); + return $session; } @@ -271,7 +293,7 @@ protected function getDefaultDriverInstance() { if (is_array($this->minkDefaultDriverArgs)) { // Use ReflectionClass to instantiate class with received params. - $reflector = new ReflectionClass($this->minkDefaultDriverClass); + $reflector = new \ReflectionClass($this->minkDefaultDriverClass); $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs); } else { @@ -317,6 +339,8 @@ protected function setUp() { $path = isset($parsed_url['path']) ? rtrim(rtrim($parsed_url['path']), '/') : ''; $port = isset($parsed_url['port']) ? $parsed_url['port'] : 80; + $this->baseUrl = $base_url; + // If the passed URL schema is 'https' then setup the $_SERVER variables // properly so that testing will run under HTTPS. if ($parsed_url['scheme'] === 'https') { @@ -1336,6 +1360,40 @@ protected function drupalUserIsLoggedIn(UserInterface $account) { } /** + * Asserts that the element with the given CSS selector is present. + * + * @param string $css_selector + * The CSS selector identifying the element to check. + * @param string $message + * Optional message to show alongside the assertion. + */ + protected function assertElementPresent($css_selector, $message = '') { + $this->assertNotEmpty($this->getSession()->getDriver()->find(CssSelector::toXPath($css_selector)), $message); + } + + /** + * Asserts that the element with the given CSS selector is not present. + * + * @param string $css_selector + * The CSS selector identifying the element to check. + * @param string $message + * Optional message to show alongside the assertion. + */ + protected function assertElementNotPresent($css_selector, $message = '') { + $this->assertEmpty($this->getSession()->getDriver()->find(CssSelector::toXPath($css_selector)), $message); + } + + /** + * Clicks the element with the given CSS selector. + * + * @param string $css_selector + * The CSS selector identifying the element to click. + */ + protected function click($css_selector) { + $this->getSession()->getDriver()->click(CssSelector::toXPath($css_selector)); + } + + /** * Prevents serializing any properties. * * Browser tests are run in a separate process. To do this PHPUnit creates a diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php index fab8833..4cecfd6 100644 --- a/core/modules/simpletest/src/TestDiscovery.php +++ b/core/modules/simpletest/src/TestDiscovery.php @@ -123,6 +123,7 @@ public function registerTestNamespaces() { $this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit"; $this->testNamespaces["Drupal\\Tests\\$name\\Kernel\\"][] = "$base_path/tests/src/Kernel"; $this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional"; + $this->testNamespaces["Drupal\\Tests\\$name\\FunctionalJavascript\\"][] = "$base_path/tests/src/FunctionalJavascript"; } foreach ($this->testNamespaces as $prefix => $paths) { diff --git a/core/modules/simpletest/tests/src/FunctionalJavascript/BrowserWithJavascriptTest.php b/core/modules/simpletest/tests/src/FunctionalJavascript/BrowserWithJavascriptTest.php new file mode 100644 index 0000000..c856203 --- /dev/null +++ b/core/modules/simpletest/tests/src/FunctionalJavascript/BrowserWithJavascriptTest.php @@ -0,0 +1,61 @@ +drupalGet(''); + $session = $this->getSession(); + + $session->resizeWindow(400, 300); + $javascript = <<assertJsCondition($javascript); + } + + public function testAssertJsCondition() { + $this->drupalGet(''); + $session = $this->getSession(); + + $session->resizeWindow(500, 300); + $javascript = <<setExpectedException(\PHPUnit_Framework_AssertionFailedError::class); + $this->assertJsCondition($javascript, 100); + } + +} diff --git a/core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarIntegrationTest.php b/core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarIntegrationTest.php new file mode 100644 index 0000000..c2354a0 --- /dev/null +++ b/core/modules/toolbar/tests/src/FunctionalJavascript/ToolbarIntegrationTest.php @@ -0,0 +1,57 @@ +drupalCreateUser([ + 'access toolbar', + 'administer site configuration', + 'access content overview' + ]); + $this->drupalLogin($admin_user); + + $this->drupalGet(''); + + // Test that it is possible to toggle the toolbar tray. + $this->assertElementVisible('#toolbar-link-system-admin_content', 'Toolbar tray is open by default.'); + $this->click('#toolbar-item-administration'); + $this->assertElementNotVisible('#toolbar-link-system-admin_content', 'Toolbar tray is closed after clicking the "Manage" button.'); + $this->click('#toolbar-item-administration'); + $this->assertElementVisible('#toolbar-link-system-admin_content', 'Toolbar tray is visible again after clicking the "Manage" button a second time.'); + + // Test toggling the toolbar tray between horizontal and vertical. + $this->assertElementVisible('#toolbar-item-administration-tray.toolbar-tray-horizontal', 'Toolbar tray is horizontally oriented by default.'); + $this->assertElementNotPresent('#toolbar-item-administration-tray.toolbar-tray-vertical', 'Toolbar tray is not vertically oriented by default.'); + + $this->click('#toolbar-item-administration-tray button.toolbar-icon-toggle-vertical'); + $this->assertJsCondition('jQuery("#toolbar-item-administration-tray").hasClass("toolbar-tray-vertical")'); + $this->assertElementVisible('#toolbar-item-administration-tray.toolbar-tray-vertical', 'After toggling the orientation the toolbar tray is now displayed vertically.'); + + $this->click('#toolbar-item-administration-tray button.toolbar-icon-toggle-horizontal'); + $this->assertJsCondition('jQuery("#toolbar-item-administration-tray").hasClass("toolbar-tray-horizontal")'); + $this->assertElementVisible('#toolbar-item-administration-tray.toolbar-tray-horizontal', 'After toggling the orientation a second time the toolbar tray is displayed horizontally again.'); + } + +} diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist index 2620fe9..4b9150e 100644 --- a/core/phpunit.xml.dist +++ b/core/phpunit.xml.dist @@ -50,6 +50,17 @@ ./drush/tests + + ./tests/Drupal/FunctionalJavascriptTests + ./modules/*/tests/src/FunctionalJavascript + ../modules/*/tests/src/FunctionalJavascript + ../profiles/*/tests/src/FunctionalJavascript + ../sites/*/modules/*/tests/src/FunctionalJavascript + + ./vendor + + ./drush/tests + diff --git a/core/tests/Drupal/FunctionalJavascriptTests/JavascriptTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptTestBase.php new file mode 100644 index 0000000..e2324c7 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/JavascriptTestBase.php @@ -0,0 +1,82 @@ +tempFilesDirectory . DIRECTORY_SEPARATOR . 'browsertestbase-templatecache'; + $this->minkDefaultDriverArgs = [ + 'http://127.0.0.1:8510', + $path, + ]; + if (!file_exists($path)) { + mkdir($path); + } + return parent::initMink(); + } + + /** + * Asserts that the element with the given CSS selector is visible. + * + * @param string $css_selector + * The CSS selector identifying the element to check. + * @param string $message + * Optional message to show alongside the assertion. + */ + protected function assertElementVisible($css_selector, $message = '') { + $this->assertTrue($this->getSession()->getDriver()->isVisible(CssSelector::toXPath($css_selector)), $message); + } + + /** + * Asserts that the element with the given CSS selector is not visible. + * + * @param string $css_selector + * The CSS selector identifying the element to check. + * @param string $message + * Optional message to show alongside the assertion. + */ + protected function assertElementNotVisible($css_selector, $message = '') { + $this->assertFalse($this->getSession()->getDriver()->isVisible(CssSelector::toXPath($css_selector)), $message); + } + + /** + * Waits for the given time or until the given JS condition becomes TRUE. + * + * @param string $condition + * JS condition to wait until it becomes TRUE. + * @param int $timeout + * (Optional) Timeout in milliseconds, defaults to 1000. + * + * @throws \PHPUnit_Framework_AssertionFailedError + */ + protected function assertJsCondition($condition, $timeout = 1000, $message = '') { + $message = $message ?: "Javascript condition met:\n" . $condition; + $result = $this->getSession()->getDriver()->wait($timeout, $condition); + $this->assertTrue($result, $message); + } + +} diff --git a/core/tests/README.md b/core/tests/README.md new file mode 100644 index 0000000..dfd1154 --- /dev/null +++ b/core/tests/README.md @@ -0,0 +1,15 @@ +# Running tests + +## Functional tests + +* Start PhantomJS: + ``` + phantomjs --ssl-protocol=any --ignore-ssl-errors=true ./vendor/jcalderonzumba/gastonjs/src/Client/main.js 8510 1024 768 2>&1 >> /dev/null & + ``` +* Run the functional tests: + ``` + export SIMPLETEST_DB='mysql://root@localhost/dev_d8' + export SIMPLETEST_BASE_URL='http://d8.dev' + ./vendor/bin/phpunit -c core --testsuite functional + ./vendor/bin/phpunit -c core --testsuite functional-javascript + ``` diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php index f1f7862..12c4deb 100644 --- a/core/tests/bootstrap.php +++ b/core/tests/bootstrap.php @@ -104,6 +104,8 @@ function drupal_phpunit_populate_class_loader() { // Start with classes in known locations. $loader->add('Drupal\\Tests', __DIR__); $loader->add('Drupal\\KernelTests', __DIR__); + $loader->add('Drupal\\FunctionalTests', __DIR__); + $loader->add('Drupal\\FunctionalJavascriptTests', __DIR__); if (!isset($GLOBALS['namespaces'])) { // Scan for arbitrary extension namespaces from core and contrib.