Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Site Health test for Cache-Control: no-store page response header which disables bfcache #1807

Merged
merged 18 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e6379b5
Add Cache-Control headers site health check functionality
b1ink0 Jan 20, 2025
2624ef4
Add unit tests for cache-control headers site health check
b1ink0 Jan 20, 2025
f65b609
Fix test name in cache-control headers unit test
b1ink0 Jan 20, 2025
abfe84e
Update Cache-Control header messages to emphasize impact on back/forw…
b1ink0 Jan 21, 2025
7b6d5da
Update function name and description to emphasize impact on back/forw…
b1ink0 Jan 21, 2025
e175dbb
Fix wrong header set for making a GET request, Update home_url() to s…
b1ink0 Jan 21, 2025
8443a8a
Return 'good' status for missing Cache-Control headers and add code c…
b1ink0 Jan 22, 2025
e0ef0c8
Rename directories and files from 'cache-control-headers' to 'bfcache…
b1ink0 Jan 22, 2025
665f5ba
Rename functions and test cases to improve clarity on bfcache compati…
b1ink0 Jan 22, 2025
b0996d7
Merge branch 'trunk' into add/cache-control-site-health-test
b1ink0 Jan 22, 2025
24963c8
Rename missed identifier to reflect effective caching headers and upd…
b1ink0 Jan 23, 2025
d275b36
Merge branch 'trunk' into add/cache-control-site-health-test
b1ink0 Jan 23, 2025
03e1a61
Rename functions for clarity in bfcache compatibility headers
b1ink0 Jan 23, 2025
c9a7134
Merge branch 'trunk' of https://github.com/WordPress/performance into…
westonruter Jan 23, 2025
f82cca9
Remove blockquote
westonruter Jan 23, 2025
96eac80
Improve strings
westonruter Jan 23, 2025
b65be42
Improve logic for checking directives
westonruter Jan 23, 2025
fe7444b
Further clarify that the bfcache is for unauthenticated requests
westonruter Jan 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php
/**
* Helper functions used for Cache-Control headers for bfcache compatibility site health check.
*
* @package performance-lab
* @since n.e.x.t
*/

// @codeCoverageIgnoreStart
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// @codeCoverageIgnoreEnd

/**
* Tests the Cache-Control headers for bfcache compatibility.
*
* @since n.e.x.t
* @access private
*
* @return array{label: string, status: string, badge: array{label: string, color: string}, description: string, actions: string, test: string} Result.
*/
function perflab_bfcache_compatibility_headers_check(): array {
$result = array(
'label' => __( 'The Cache-Control page header is compatible with fast back/forward navigations', 'performance-lab' ),
'status' => 'good',
'badge' => array(
'label' => __( 'Performance', 'performance-lab' ),
'color' => 'blue',
),
'description' => '<p>' . wp_kses(
__( 'If the <code>Cache-Control</code> page response header includes directives like <code>no-store</code>, <code>no-cache</code>, or <code>max-age=0</code> then it can prevent instant back/forward navigations (using the browser bfcache). These are not present for unauthenticated requests on your site, so it is configured properly. Note that WordPress adds these directives for logged-in page responses.', 'performance-lab' ),
array( 'code' => array() )
) . '</p>',
'actions' => '',
'test' => 'perflab_cch_cache_control_header_check',
);

$response = wp_remote_get(
home_url( '/' ),
array(
'headers' => array( 'Accept' => 'text/html' ),
'sslverify' => false,
)
);

if ( is_wp_error( $response ) ) {
$result['label'] = __( 'Unable to check whether the Cache-Control page header is compatible with fast back/forward navigations', 'performance-lab' );
$result['status'] = 'recommended';
$result['description'] = '<p>' . wp_kses(
sprintf(
/* translators: 1: the error code, 2: the error message */
__( 'The unauthenticated request to check the <code>Cache-Control</code> response header for the home page resulted in an error with code <code>%1$s</code> and the following message: %2$s.', 'performance-lab' ),
esc_html( (string) $response->get_error_code() ),
esc_html( rtrim( $response->get_error_message(), '.' ) )
),
array( 'code' => array() )
) . '</p>';
return $result;
}

$cache_control_headers = wp_remote_retrieve_header( $response, 'cache-control' );
if ( '' === $cache_control_headers ) {
// The Cache-Control header is not set, so it does not prevent bfcache. Return the default result.
return $result;
}

foreach ( (array) $cache_control_headers as $cache_control_header ) {
$cache_control_header = strtolower( $cache_control_header );
$found_directives = array();
foreach ( array( 'no-store', 'no-cache', 'max-age=0' ) as $directive ) {
if ( str_contains( $cache_control_header, $directive ) ) {
$found_directives[] = $directive;
}
}

if ( count( $found_directives ) > 0 ) {
$result['label'] = __( 'The Cache-Control page header is preventing fast back/forward navigations', 'performance-lab' );
$result['status'] = 'recommended';
$result['description'] = sprintf(
'<p>%s %s</p>',
wp_kses(
sprintf(
/* translators: %s: problematic directive(s) */
_n(
'The <code>Cache-Control</code> response header for an unauthenticated request to the home page includes the following directive: %s.',
'The <code>Cache-Control</code> response header for an unauthenticated request to the home page includes the following directives: %s.',
count( $found_directives ),
'performance-lab'
),
implode(
', ',
array_map(
static function ( $header ) {
return "<code>$header</code>";
},
$found_directives
)
)
),
array( 'code' => array() )
),
esc_html__( 'This can affect the performance of your site by preventing fast back/forward navigations (via browser bfcache).', 'performance-lab' )
);
break;
}
}

return $result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
/**
* Hook callbacks used for cache-control headers.
*
* @package performance-lab
* @since n.e.x.t
*/

// @codeCoverageIgnoreStart
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
// @codeCoverageIgnoreEnd

/**
* Add the bfcache compatibility check to site health tests.
*
* @since n.e.x.t
* @access private
*
* @param array{direct: array<string, array{label: string, test: string}>} $tests Site Health Tests.
* @return array{direct: array<string, array{label: string, test: string}>} Amended tests.
*/
function perflab_bfcache_compatibility_headers_add_test( array $tests ): array {
$tests['direct']['perflab_bfcache_compatibility_headers'] = array(
'label' => __( 'Cache-Control headers may prevent fast back/forward navigation', 'performance-lab' ),
'test' => 'perflab_bfcache_compatibility_headers_check',
);
return $tests;
}
add_filter( 'site_status_tests', 'perflab_bfcache_compatibility_headers_add_test' );

Check warning on line 31 in plugins/performance-lab/includes/site-health/bfcache-compatibility-headers/hooks.php

View check run for this annotation

Codecov / codecov/patch

plugins/performance-lab/includes/site-health/bfcache-compatibility-headers/hooks.php#L31

Added line #L31 was not covered by tests
4 changes: 4 additions & 0 deletions plugins/performance-lab/includes/site-health/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@
// Effective Asset Cache Headers site health check.
require_once __DIR__ . '/effective-asset-cache-headers/helper.php';
require_once __DIR__ . '/effective-asset-cache-headers/hooks.php';

// Cache-Control headers site health check.
require_once __DIR__ . '/bfcache-compatibility-headers/helper.php';
require_once __DIR__ . '/bfcache-compatibility-headers/hooks.php';

Check warning on line 41 in plugins/performance-lab/includes/site-health/load.php

View check run for this annotation

Codecov / codecov/patch

plugins/performance-lab/includes/site-health/load.php#L40-L41

Added lines #L40 - L41 were not covered by tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php
/**
* Tests for cache-control headers for bfcache compatibility site health check.
*
* @package performance-lab
* @group bfcache-compatibility-headers
*/

class Test_BFCache_Compatibility_Headers extends WP_UnitTestCase {

/**
* Holds mocked response headers for different test scenarios.
*
* @var array<string, array<string, mixed>>
*/
protected $mocked_responses = array();

/**
* Setup each test.
*/
public function setUp(): void {
parent::setUp();

// Clear any filters or mocks.
remove_all_filters( 'pre_http_request' );

// Add the filter to mock HTTP requests.
add_filter( 'pre_http_request', array( $this, 'mock_http_requests' ), 10, 3 );
}

/**
* Test that the bfcache compatibility test is added to the site health tests.
*
* @covers ::perflab_bfcache_compatibility_headers_add_test
*/
public function test_perflab_bfcache_compatibility_headers_add_test(): void {
$tests = array(
'direct' => array(),
);

$tests = perflab_bfcache_compatibility_headers_add_test( $tests );
$this->assertArrayHasKey( 'perflab_bfcache_compatibility_headers', $tests['direct'] );
$this->assertEquals( 'Cache-Control headers may prevent fast back/forward navigation', $tests['direct']['perflab_bfcache_compatibility_headers']['label'] );
$this->assertEquals( 'perflab_bfcache_compatibility_headers_check', $tests['direct']['perflab_bfcache_compatibility_headers']['test'] );
}

/**
* Test that the bfcache compatibility test is attached to the site status tests.
*
* @covers ::perflab_bfcache_compatibility_headers_add_test
*/
public function test_perflab_bfcache_compatibility_headers_add_test_is_attached(): void {
$this->assertNotFalse( has_filter( 'site_status_tests', 'perflab_bfcache_compatibility_headers_add_test' ) );
}

/**
* Test that different Cache-Control headers return the correct bfcache compatibility result.
*
* @dataProvider data_test_bfcache_compatibility
* @covers ::perflab_bfcache_compatibility_headers_check
*
* @param array<int, mixed>|WP_Error $response The response headers.
* @param string $expected_status The expected status.
* @param string $expected_message The expected message.
*/
public function test_perflab_bfcache_compatibility_headers_check( $response, string $expected_status, string $expected_message ): void {
$this->mocked_responses = array( home_url( '/' ) => $response );

$result = perflab_bfcache_compatibility_headers_check();

$this->assertEquals( $expected_status, $result['status'] );
$this->assertStringContainsString( $expected_message, $result['description'] );
}

/**
* Data provider for bfcache compatibility tests.
*
* @return array<string, array<int, mixed>> Test data.
*/
public function data_test_bfcache_compatibility(): array {
return array(
'headers_not_set' => array(
$this->build_response( 200, array( 'cache-control' => '' ) ),
'good',
'If the <code>Cache-Control</code> page response header includes directives like',
),
'no_store' => array(
$this->build_response( 200, array( 'cache-control' => 'no-store' ) ),
'recommended',
'<p>The <code>Cache-Control</code> response header for an unauthenticated request to the home page includes the following directive: <code>no-store</code>',
),
'no_cache' => array(
$this->build_response( 200, array( 'cache-control' => 'no-cache' ) ),
'recommended',
'<p>The <code>Cache-Control</code> response header for an unauthenticated request to the home page includes the following directive: <code>no-cache</code>',
),
'max_age_0' => array(
$this->build_response( 200, array( 'cache-control' => 'max-age=0' ) ),
'recommended',
'<p>The <code>Cache-Control</code> response header for an unauthenticated request to the home page includes the following directive: <code>max-age=0</code>',
),
'max_age_0_no_store' => array(
$this->build_response( 200, array( 'cache-control' => 'max-age=0, no-store' ) ),
'recommended',
'<p>The <code>Cache-Control</code> response header for an unauthenticated request to the home page includes the following directives: <code>no-store</code>, <code>max-age=0</code>',
),
'error' => array(
new WP_Error( 'http_request_failed', 'HTTP request failed' ),
'recommended',
'The unauthenticated request to check the <code>Cache-Control</code> response header for the home page resulted in an error with code',
),
);
}

/**
* Mock HTTP requests for assets to simulate different responses.
*
* @param bool $response A preemptive return value of an HTTP request. Default false.
* @param array<string, mixed> $args Request arguments.
* @param string $url The request URL.
* @return array<string, mixed>|WP_Error Mocked response.
*/
public function mock_http_requests( bool $response, array $args, string $url ) {
if ( isset( $this->mocked_responses[ $url ] ) ) {
return $this->mocked_responses[ $url ];
}

// If no specific mock set, default to a generic success with no caching.
return $this->build_response( 200 );
}

/**
* Helper method to build a mock HTTP response.
*
* @param int $status_code HTTP status code.
* @param array<string, string|int> $headers HTTP headers.
* @return array{response: array{code: int, message: string}, headers: WpOrg\Requests\Utility\CaseInsensitiveDictionary}
*/
protected function build_response( int $status_code = 200, array $headers = array() ): array {
return array(
'response' => array(
'code' => $status_code,
'message' => '',
),
'headers' => new WpOrg\Requests\Utility\CaseInsensitiveDictionary( $headers ),
);
}
}
Loading