3 use MediaWiki\Context\DerivativeContext
;
4 use MediaWiki\Context\RequestContext
;
5 use MediaWiki\Output\OutputPage
;
6 use MediaWiki\Request\FauxRequest
;
7 use MediaWiki\ResourceLoader\Context
;
8 use MediaWiki\ResourceLoader\Module
;
9 use MediaWiki\User\User
;
15 class PerformanceBudgetTest
extends MediaWikiIntegrationTestCase
{
17 * Calculates the size of a module
19 * @param array $moduleNames
20 * @param string $skinName
21 * @param bool $isScripts
24 * @throws \Wikimedia\RequestTimeout\TimeoutException
25 * @throws MediaWiki\Config\ConfigException
27 protected function getContentTransferSize( $moduleNames, $skinName, $isScripts = false ) {
29 $resourceLoader = $this->getServiceContainer()->getResourceLoader();
30 $request = new FauxRequest(
33 'modules' => implode( '|', $moduleNames ),
38 foreach ( $moduleNames as $moduleName ) {
39 $modules[ $moduleName ] = $resourceLoader->getModule( $moduleName );
42 $context = new Context( $resourceLoader, $request );
43 $contentContext = new \MediaWiki\ResourceLoader\
DerivativeContext( $context );
44 $contentContext->setOnly(
46 ? Module
::TYPE_COMBINED
49 // Create a module response for the given module and calculate the size
50 $content = $resourceLoader->makeModuleResponse( $contentContext, $modules );
51 $contentTransferSize = strlen( gzencode( $content, 9 ) );
52 return $contentTransferSize;
56 * Prepares a skin for testing, assigning context and output page
58 * @param string $skinName
61 * @throws \SkinException
63 protected function prepareSkin( string $skinName ): \Skin
{
64 $skinFactory = $this->getServiceContainer()->getSkinFactory();
65 $skin = $skinFactory->makeSkin( $skinName );
66 $title = $this->getExistingTestPage()->getTitle();
67 $context = new DerivativeContext( RequestContext
::getMain() );
69 $context->setUser( $anon );
70 $context->setTitle( $title );
71 $context->setSkin( $skin );
72 $outputPage = new OutputPage( $context );
73 $context->setOutput( $outputPage );
74 $skin->setContext( $context );
75 $outputPage->setTitle( $title );
76 $outputPage->output( true );
81 * Converts a string to bytes
83 * @param string|int|float $size
87 private function getSizeInBytes( $size ) {
88 if ( is_string( $size ) ) {
89 if ( strpos( $size, 'KB' ) !== false ||
strpos( $size, 'kB' ) !== false ) {
90 $size = (float)str_replace( [ 'KB', 'kB', ' KB', ' kB' ], '', $size );
92 } elseif ( strpos( $size, 'B' ) !== false ) {
93 $size = (float)str_replace( [ ' B', 'B' ], '', $size );
100 * @param string $skinName
101 * @param array $moduleNames
102 * @param bool $isScripts
104 private function testModuleSizes( $skinName, $moduleNames, $isScripts = false ) {
105 $size = $this->getContentTransferSize( $moduleNames, $skinName, $isScripts );
107 $moduleType = $isScripts ?
'scripts' : 'styles';
108 $sizeKb = ceil( ( $size * 10 ) / 1024 ) / 10;
109 $warning = "Total size of $moduleType modules is " . $sizeKb . "kB.\n" .
110 "If you are adding code on page load, please reduce $moduleType that you are loading on page load.\n" .
111 "Read https://www.mediawiki.org/wiki/Performance_budgeting for more context on this number.\n\n";
113 $this->markTestSkipped( 'Tests are non-blocking for now.' );
117 * Tests the size of modules in allowed skins
122 * @throws \Wikimedia\RequestTimeout\TimeoutException
123 * @throws MediaWiki\Config\ConfigException
125 public function testTotalModulesSize() {
126 $skinName = 'vector-2022';
127 $skin = $this->prepareSkin( $skinName );
128 $moduleStyles = $skin->getOutput()->getModuleStyles();
129 $moduleScripts = $skin->getOutput()->getModules();
130 $this->testModuleSizes( $skinName, $moduleStyles );
131 $this->testModuleSizes( $skinName, $moduleScripts, true );