diff --git a/core/modules/system/tests/src/Functional/Common/NoJavaScriptAnonymousTest.php b/core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php similarity index 87% rename from core/modules/system/tests/src/Functional/Common/NoJavaScriptAnonymousTest.php rename to core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php index 0cb676e28c5ba7af21591b35d785760543cca8fb..31db159593d35dacec3b93988626f1234527d4d1 100644 --- a/core/modules/system/tests/src/Functional/Common/NoJavaScriptAnonymousTest.php +++ b/core/modules/system/tests/src/FunctionalJavascript/NoJavaScriptAnonymousTest.php @@ -1,9 +1,9 @@ <?php -namespace Drupal\Tests\system\Functional\Common; +namespace Drupal\Tests\system\FunctionalJavaScript; +use Drupal\FunctionalJavascriptTests\PerformanceTestBase; use Drupal\node\NodeInterface; -use Drupal\Tests\BrowserTestBase; /** * Tests that anonymous users are not served any JavaScript. @@ -13,7 +13,7 @@ * * @group Common */ -class NoJavaScriptAnonymousTest extends BrowserTestBase { +class NoJavaScriptAnonymousTest extends PerformanceTestBase { /** * {@inheritdoc} @@ -69,6 +69,7 @@ protected function assertNoJavaScript(): void { $settings = $this->getDrupalSettings(); $this->assertEmpty($settings, 'drupalSettings is not set.'); $this->assertSession()->responseNotMatches('/\.js/'); + $this->assertSame(0, $this->scriptCount); } } diff --git a/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php b/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5d5462db17424357bb32659d20ec1933f80bec76 --- /dev/null +++ b/core/profiles/demo_umami/tests/src/FunctionalJavascript/PerformanceTest.php @@ -0,0 +1,41 @@ +<?php + +namespace Drupal\Tests\demo_umami\FunctionalJavascript; + +use Drupal\FunctionalJavascriptTests\PerformanceTestBase; + +/** + * Tests demo_umami profile performance. + * + * @group performance + */ +class PerformanceTest extends PerformanceTestBase { + + /** + * {@inheritdoc} + */ + protected $profile = 'demo_umami'; + + /** + * Just load the front page. + */ + public function testFrontPage(): void { + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('Umami'); + $this->assertSame(2, $this->stylesheetCount); + $this->assertSame(1, $this->scriptCount); + } + + /** + * Load the front page as a user with access to Tours. + */ + public function testFrontPageTour(): void { + $admin_user = $this->drupalCreateUser(['access tour']); + $this->drupalLogin($admin_user); + $this->drupalGet('<front>'); + $this->assertSession()->pageTextContains('Umami'); + $this->assertSame(2, $this->stylesheetCount); + $this->assertSame(1, $this->scriptCount); + } + +} diff --git a/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..9d247a12b4953472c9814ed5ec463540eff4ece1 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/PerformanceTestBase.php @@ -0,0 +1,123 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\FunctionalJavascriptTests; + +use Drupal\Core\Url; +use Drupal\Tests\BrowserTestBase; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Collects performance metrics. + * + * @ingroup testing + */ +class PerformanceTestBase extends WebDriverTestBase { + + /** + * The number of stylesheets requested. + */ + protected int $stylesheetCount = 0; + + /** + * The number of scripts requested. + */ + protected int $scriptCount = 0; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + \Drupal::configFactory()->getEditable('system.performance') + ->set('css.preprocess', TRUE) + ->set('js.preprocess', TRUE) + ->save(); + } + + /** + * {@inheritdoc} + */ + protected function installModulesFromClassProperty(ContainerInterface $container) { + // Bypass everything that WebDriverTestBase does here to get closer to + // a production configuration. + BrowserTestBase::installModulesFromClassProperty($container); + } + + /** + * {@inheritdoc} + */ + protected function getMinkDriverArgs() { + + // Add performance logging preferences to the existing driver arguments to + // avoid clobbering anything set via environment variables. + // @see https://chromedriver.chromium.org/logging/performance-log + $parent_driver_args = parent::getMinkDriverArgs(); + $driver_args = json_decode($parent_driver_args, TRUE); + + $driver_args[1]['goog:loggingPrefs'] = [ + 'browser' => 'ALL', + 'performance' => 'ALL', + 'performanceTimeline' => 'ALL', + ]; + $driver_args[1]['chromeOptions']['perfLoggingPrefs'] = [ + 'traceCategories' => 'devtools.timeline', + 'enableNetwork' => TRUE, + ]; + + return json_encode($driver_args); + } + + /** + * {@inheritdoc} + */ + public function drupalGet($path, array $options = [], array $headers = []): string { + // Reset the performance log from any previous HTTP requests. The log is + // cumulative until it is collected explicitly. + $session = $this->getSession(); + $session->getDriver()->getWebDriverSession()->log('performance'); + $return = parent::drupalGet($path, $options, $headers); + $this->getChromeDriverPerformanceMetrics($path); + return $return; + } + + /** + * Gets the chromedriver performance log and extracts metrics from it. + */ + protected function getChromeDriverPerformanceMetrics(string|Url $path): void { + $session = $this->getSession(); + $performance_log = $session->getDriver()->getWebDriverSession()->log('performance'); + + $messages = []; + foreach ($performance_log as $entry) { + $decoded = json_decode($entry['message'], TRUE); + $messages[] = $decoded['message']; + } + $this->collectNetworkData($path, $messages); + } + + /** + * Prepares data for assertions. + * + * @param string|\Drupal\Core\Url $path + * The path as passed to static::drupalGet(). + * @param array $messages + * The chromedriver performance log messages. + */ + protected function collectNetworkData(string|Url $path, array $messages): void { + $this->stylesheetCount = 0; + $this->scriptCount = 0; + foreach ($messages as $message) { + if ($message['method'] === 'Network.responseReceived') { + if ($message['params']['type'] === 'Stylesheet') { + $this->stylesheetCount++; + } + if ($message['params']['type'] === 'Script') { + $this->scriptCount++; + } + } + } + } + +}