From dc9e36439a3c75e181b6bdb1548e46c95658e4c1 Mon Sep 17 00:00:00 2001 From: b1ink0 Date: Mon, 9 Dec 2024 18:05:17 +0530 Subject: [PATCH 01/54] Add site health checks for far-future expiration headers --- .../far-future-headers/assets/test.css | 0 .../site-health/far-future-headers/helper.php | 115 ++++++++++++++++++ .../site-health/far-future-headers/hooks.php | 28 +++++ .../includes/site-health/load.php | 4 + 4 files changed, 147 insertions(+) create mode 100644 plugins/performance-lab/includes/site-health/far-future-headers/assets/test.css create mode 100644 plugins/performance-lab/includes/site-health/far-future-headers/helper.php create mode 100644 plugins/performance-lab/includes/site-health/far-future-headers/hooks.php diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/assets/test.css b/plugins/performance-lab/includes/site-health/far-future-headers/assets/test.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php new file mode 100644 index 0000000000..180c51cb53 --- /dev/null +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -0,0 +1,115 @@ + __( 'Your site serves static assets with far-future expiration headers', 'performance-lab' ), + 'status' => 'good', + 'badge' => array( + 'label' => __( 'Performance', 'performance-lab' ), + 'color' => 'blue', + ), + 'description' => sprintf( + '

%s

', + esc_html__( + 'Serving static assets with far-future expiration headers improves performance by allowing browsers to cache files for a long time, reducing repeated requests.', + 'performance-lab' + ) + ), + 'actions' => '', + 'test' => 'is_far_future_headers_enabled', + ); + + // List of assets to check. + $assets = array( + plugins_url( 'far-future-headers/assets/test.css', __DIR__ ), + ); + + // Check if far-future headers are enabled for all assets. + $far_future_enabled = true; + foreach ( $assets as $asset ) { + if ( ! perflab_far_future_headers_is_enabled( $asset ) ) { + $far_future_enabled = false; + break; + } + } + + if ( ! $far_future_enabled ) { + $result['status'] = 'recommended'; + $result['label'] = __( 'Your site does not serve static assets with recommended far-future expiration headers', 'performance-lab' ); + $result['actions'] = sprintf( + '

%s

', + esc_html__( 'Consider adding or adjusting your server configuration (e.g., .htaccess, nginx config, or a caching plugin) to include far-future Cache-Control or Expires headers for static assets.', 'performance-lab' ) + ); + } + + return $result; +} + +/** + * Checks if far-future expiration headers are enabled. + * + * @since n.e.x.t + * + * @param string $url URL to check. + * @return bool True if far-future headers are enabled, false otherwise. + */ +function perflab_far_future_headers_is_enabled( string $url ): bool { + $threshold = YEAR_IN_SECONDS; + + $response = wp_remote_request( $url, array( 'sslverify' => false ) ); + + if ( is_wp_error( $response ) ) { + return false; + } + + $headers = wp_remote_retrieve_headers( $response ); + + $cache_control = isset( $headers['cache-control'] ) ? $headers['cache-control'] : ''; + $expires = isset( $headers['expires'] ) ? $headers['expires'] : ''; + + // Check Cache-Control header for max-age. + $max_age = 0; + if ( $cache_control ) { + // Cache-Control can have multiple directives; we only care about max-age. + $controls = is_array( $cache_control ) ? $cache_control : array( $cache_control ); + foreach ( $controls as $control ) { + if ( (bool) preg_match( '/max-age\s*=\s*(\d+)/', $control, $matches ) ) { + $max_age = (int) $matches[1]; + break; + } + } + } + + // If max-age meets or exceeds the threshold, we consider it good. + if ( $max_age >= $threshold ) { + return true; + } + + // If max-age is not sufficient, check Expires. + // Expires is a date; we want to ensure it's far in the future. + if ( $expires ) { + $expires_time = strtotime( $expires ); + if ( $expires_time && ( $expires_time - time() ) >= $threshold ) { + return true; + } + } + + return false; +} diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/hooks.php b/plugins/performance-lab/includes/site-health/far-future-headers/hooks.php new file mode 100644 index 0000000000..9ca091a167 --- /dev/null +++ b/plugins/performance-lab/includes/site-health/far-future-headers/hooks.php @@ -0,0 +1,28 @@ +} $tests Site Health Tests. + * @return array{direct: array} Amended tests. + */ +function perflab_ffh_add_test( array $tests ): array { + $tests['direct']['far_future_headers'] = array( + 'label' => __( 'Far-Future Caching Headers', 'performance-lab' ), + 'test' => 'perflab_ffh_assets_test', + ); + return $tests; +} +add_filter( 'site_status_tests', 'perflab_ffh_add_test' ); diff --git a/plugins/performance-lab/includes/site-health/load.php b/plugins/performance-lab/includes/site-health/load.php index e4cd71a596..ac0032cf87 100644 --- a/plugins/performance-lab/includes/site-health/load.php +++ b/plugins/performance-lab/includes/site-health/load.php @@ -29,3 +29,7 @@ // AVIF headers site health check. require_once __DIR__ . '/avif-headers/helper.php'; require_once __DIR__ . '/avif-headers/hooks.php'; + +// Far-Future Headers site health check. +require_once __DIR__ . '/far-future-headers/helper.php'; +require_once __DIR__ . '/far-future-headers/hooks.php'; From 4cb07aac678ad596d1d8bb6cd8c8a50ca7584b2b Mon Sep 17 00:00:00 2001 From: b1ink0 Date: Mon, 9 Dec 2024 18:25:49 +0530 Subject: [PATCH 02/54] Add filter for customizing far-future headers threshold --- .../includes/site-health/far-future-headers/helper.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 180c51cb53..5ac2b2963c 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -71,7 +71,14 @@ function perflab_ffh_assets_test(): array { * @return bool True if far-future headers are enabled, false otherwise. */ function perflab_far_future_headers_is_enabled( string $url ): bool { - $threshold = YEAR_IN_SECONDS; + /** + * Filters the threshold for far-future headers. + * + * @since n.e.x.t + * + * @param int $threshold Threshold in seconds. + */ + $threshold = apply_filters( 'perflab_far_future_headers_threshold', YEAR_IN_SECONDS ); $response = wp_remote_request( $url, array( 'sslverify' => false ) ); From ec9a552b2450f4a7aeabeee7dfb7ab15f6982c1c Mon Sep 17 00:00:00 2001 From: b1ink0 Date: Mon, 9 Dec 2024 19:52:47 +0530 Subject: [PATCH 03/54] Refactor far-future headers checks and improve asset validation logic --- .../site-health/far-future-headers/helper.php | 107 ++++++++++++++---- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 5ac2b2963c..0cd8b1092f 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -42,35 +42,69 @@ function perflab_ffh_assets_test(): array { ); // Check if far-future headers are enabled for all assets. - $far_future_enabled = true; - foreach ( $assets as $asset ) { - if ( ! perflab_far_future_headers_is_enabled( $asset ) ) { - $far_future_enabled = false; - break; - } - } + $status = perflab_ffh_check_assets( $assets ); - if ( ! $far_future_enabled ) { - $result['status'] = 'recommended'; + if ( 'good' !== $status ) { + $result['status'] = $status; $result['label'] = __( 'Your site does not serve static assets with recommended far-future expiration headers', 'performance-lab' ); $result['actions'] = sprintf( '

%s

', - esc_html__( 'Consider adding or adjusting your server configuration (e.g., .htaccess, nginx config, or a caching plugin) to include far-future Cache-Control or Expires headers for static assets.', 'performance-lab' ) + esc_html__( 'Far-future Cache-Control or Expires headers can be added or adjusted with a small configuration change by your hosting provider.', 'performance-lab' ) ); } return $result; } +/** + * Checks if far-future expiration headers are enabled for a list of assets. + * + * @since n.e.x.t + * + * @param string[] $assets List of asset URLs to check. + * @return string 'good' if far-future headers are enabled for all assets, 'recommended' if some assets are missing headers. + */ +function perflab_ffh_check_assets( array $assets ): string { + $final_status = 'good'; + + foreach ( $assets as $asset ) { + $response = wp_remote_get( $asset, array( 'sslverify' => false ) ); + + if ( is_wp_error( $response ) ) { + continue; + } + + $headers = wp_remote_retrieve_headers( $response ); + + if ( is_array( $headers ) ) { + continue; + } + + if ( perflab_ffh_check_headers( $headers ) ) { + continue; + } + + // If far-future headers are not enabled, attempt a conditional request. + if ( perflab_ffh_try_conditional_request( $asset, $headers ) ) { + $final_status = 'recommended'; + continue; + } + + $final_status = 'recommended'; + } + + return $final_status; +} + /** * Checks if far-future expiration headers are enabled. * * @since n.e.x.t * - * @param string $url URL to check. + * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers Response headers. * @return bool True if far-future headers are enabled, false otherwise. */ -function perflab_far_future_headers_is_enabled( string $url ): bool { +function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers ): bool { /** * Filters the threshold for far-future headers. * @@ -80,20 +114,12 @@ function perflab_far_future_headers_is_enabled( string $url ): bool { */ $threshold = apply_filters( 'perflab_far_future_headers_threshold', YEAR_IN_SECONDS ); - $response = wp_remote_request( $url, array( 'sslverify' => false ) ); - - if ( is_wp_error( $response ) ) { - return false; - } - - $headers = wp_remote_retrieve_headers( $response ); - $cache_control = isset( $headers['cache-control'] ) ? $headers['cache-control'] : ''; $expires = isset( $headers['expires'] ) ? $headers['expires'] : ''; // Check Cache-Control header for max-age. $max_age = 0; - if ( $cache_control ) { + if ( '' !== $cache_control ) { // Cache-Control can have multiple directives; we only care about max-age. $controls = is_array( $cache_control ) ? $cache_control : array( $cache_control ); foreach ( $controls as $control ) { @@ -111,12 +137,47 @@ function perflab_far_future_headers_is_enabled( string $url ): bool { // If max-age is not sufficient, check Expires. // Expires is a date; we want to ensure it's far in the future. - if ( $expires ) { + if ( is_string( $expires ) && '' !== $expires ) { $expires_time = strtotime( $expires ); - if ( $expires_time && ( $expires_time - time() ) >= $threshold ) { + if ( (bool) $expires_time && ( $expires_time - time() ) >= $threshold ) { return true; } } return false; } + +/** + * Attempt a conditional request with ETag/Last-Modified. + * + * @param string $url The asset URL. + * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers The initial response headers. + * @return bool True if a 304 response was received. + */ +function perflab_ffh_try_conditional_request( string $url, WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers ): bool { + $etag = isset( $headers['etag'] ) ? $headers['etag'] : ''; + $last_modified = isset( $headers['last-modified'] ) ? $headers['last-modified'] : ''; + + $conditional_headers = array(); + if ( '' !== $etag ) { + $conditional_headers['If-None-Match'] = $etag; + } + if ( '' !== $last_modified ) { + $conditional_headers['If-Modified-Since'] = $last_modified; + } + + $response = wp_remote_get( + $url, + array( + 'sslverify' => false, + 'headers' => $conditional_headers, + ) + ); + + if ( is_wp_error( $response ) ) { + return false; + } + + $status_code = wp_remote_retrieve_response_code( $response ); + return ( 304 === $status_code ); +} From 0078cfc151d072d06a2ec0ab3a1d5e9fd9219329 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 10 Dec 2024 14:05:54 +0530 Subject: [PATCH 04/54] Use assets from wp-includes for far-future headers site health check --- .../includes/site-health/far-future-headers/assets/test.css | 0 .../includes/site-health/far-future-headers/helper.php | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 plugins/performance-lab/includes/site-health/far-future-headers/assets/test.css diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/assets/test.css b/plugins/performance-lab/includes/site-health/far-future-headers/assets/test.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 0cd8b1092f..c6cc7824ec 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -38,7 +38,10 @@ function perflab_ffh_assets_test(): array { // List of assets to check. $assets = array( - plugins_url( 'far-future-headers/assets/test.css', __DIR__ ), + includes_url( 'js/wp-embed.min.js' ), + includes_url( 'css/buttons.min.css' ), + includes_url( 'fonts/dashicons.woff2' ), + includes_url( 'images/media/video.png' ), ); // Check if far-future headers are enabled for all assets. From 3e5d958c424315f1863f66a29fa581004d272010 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 10 Dec 2024 14:08:47 +0530 Subject: [PATCH 05/54] Add filter to customize assets checked for far-future headers --- .../includes/site-health/far-future-headers/helper.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index c6cc7824ec..8edf4e76e5 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -44,6 +44,15 @@ function perflab_ffh_assets_test(): array { includes_url( 'images/media/video.png' ), ); + /** + * Filters the list of assets to check for far-future headers. + * + * @since n.e.x.t + * + * @param string[] $assets List of asset URLs to check. + */ + $assets = apply_filters( 'perflab_ffh_assets_to_check', $assets ); + // Check if far-future headers are enabled for all assets. $status = perflab_ffh_check_assets( $assets ); From 35e79775d7b4a0188543b0be2852f459505c06aa Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 10 Dec 2024 14:58:07 +0530 Subject: [PATCH 06/54] Enhance far-future headers check to return detailed results and generate a report table for missing headers --- .../site-health/far-future-headers/helper.php | 93 ++++++++++++++----- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 8edf4e76e5..03c9fbdc41 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -54,15 +54,25 @@ function perflab_ffh_assets_test(): array { $assets = apply_filters( 'perflab_ffh_assets_to_check', $assets ); // Check if far-future headers are enabled for all assets. - $status = perflab_ffh_check_assets( $assets ); - - if ( 'good' !== $status ) { - $result['status'] = $status; - $result['label'] = __( 'Your site does not serve static assets with recommended far-future expiration headers', 'performance-lab' ); - $result['actions'] = sprintf( - '

%s

', - esc_html__( 'Far-future Cache-Control or Expires headers can be added or adjusted with a small configuration change by your hosting provider.', 'performance-lab' ) - ); + $results = perflab_ffh_check_assets( $assets ); + + if ( 'good' !== $results['final_status'] ) { + $result['status'] = $results['final_status']; + $result['label'] = __( 'Your site does not serve static assets with recommended far-future expiration headers', 'performance-lab' ); + + if ( count( $results['details'] ) > 0 ) { + $table_html = perflab_ffh_get_extensions_table( $results['details'] ); + $result['actions'] = sprintf( + '

%s

%s', + esc_html__( 'The following file types do not have the recommended far-future headers. Consider adding or adjusting Cache-Control or Expires headers for these asset types.', 'performance-lab' ), + $table_html + ); + } else { + $result['actions'] = sprintf( + '

%s

', + esc_html__( 'Far-future Cache-Control or Expires headers can be added or adjusted with a small configuration change by your hosting provider.', 'performance-lab' ) + ); + } } return $result; @@ -74,38 +84,45 @@ function perflab_ffh_assets_test(): array { * @since n.e.x.t * * @param string[] $assets List of asset URLs to check. - * @return string 'good' if far-future headers are enabled for all assets, 'recommended' if some assets are missing headers. + * @return array{final_status: string, details: string[]} Final status and details. */ -function perflab_ffh_check_assets( array $assets ): string { - $final_status = 'good'; +function perflab_ffh_check_assets( array $assets ): array { + $final_status = 'good'; + $extension_results = array(); // Extensions that need improvement. foreach ( $assets as $asset ) { $response = wp_remote_get( $asset, array( 'sslverify' => false ) ); + // Extract extension from the URL. + $path_info = pathinfo( (string) wp_parse_url( $asset, PHP_URL_PATH ) ); + $extension = isset( $path_info['extension'] ) ? strtolower( $path_info['extension'] ) : 'unknown'; + if ( is_wp_error( $response ) ) { continue; } $headers = wp_remote_retrieve_headers( $response ); - - if ( is_array( $headers ) ) { + if ( ! is_object( $headers ) ) { continue; } - if ( perflab_ffh_check_headers( $headers ) ) { - continue; - } + if ( ! perflab_ffh_check_headers( $headers ) ) { + if ( ! perflab_ffh_try_conditional_request( $asset, $headers ) ) { + $final_status = 'recommended'; + $extension_results[] = $extension; + continue; + } - // If far-future headers are not enabled, attempt a conditional request. - if ( perflab_ffh_try_conditional_request( $asset, $headers ) ) { - $final_status = 'recommended'; - continue; + // Conditional pass means still recommended, not fully good. + $final_status = 'recommended'; + $extension_results[] = $extension; } - - $final_status = 'recommended'; } - return $final_status; + return array( + 'final_status' => $final_status, + 'details' => $extension_results, + ); } /** @@ -193,3 +210,31 @@ function perflab_ffh_try_conditional_request( string $url, WpOrg\Requests\Utilit $status_code = wp_remote_retrieve_response_code( $response ); return ( 304 === $status_code ); } + +/** + * Generate a table listing file extensions that need far-future headers. + * + * @since n.e.x.t + * + * @param string[] $extensions Array of file extensions needing improvement. + * @return string HTML formatted table. + */ +function perflab_ffh_get_extensions_table( array $extensions ): string { + $html_table = sprintf( + '', + esc_html__( 'File Extension', 'performance-lab' ), + esc_html__( 'Status', 'performance-lab' ) + ); + + foreach ( $extensions as $extension ) { + $html_table .= sprintf( + '', + esc_html( $extension ), + esc_html__( 'Needs far-future headers', 'performance-lab' ) + ); + } + + $html_table .= '
%s%s
%s%s
'; + + return $html_table; +} From 3db58860f1bbbd47a23824e05adb2aa8938d6125 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Tue, 10 Dec 2024 16:03:34 +0530 Subject: [PATCH 07/54] Improve far-future headers checks by adding detailed failure reasons and updating the extensions table generation --- .../site-health/far-future-headers/helper.php | 117 +++++++++++++----- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 03c9fbdc41..ae226b608e 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -61,11 +61,11 @@ function perflab_ffh_assets_test(): array { $result['label'] = __( 'Your site does not serve static assets with recommended far-future expiration headers', 'performance-lab' ); if ( count( $results['details'] ) > 0 ) { - $table_html = perflab_ffh_get_extensions_table( $results['details'] ); $result['actions'] = sprintf( - '

%s

%s', + '

%s

%s

%s

', esc_html__( 'The following file types do not have the recommended far-future headers. Consider adding or adjusting Cache-Control or Expires headers for these asset types.', 'performance-lab' ), - $table_html + perflab_ffh_get_extensions_table( $results['details'] ), + esc_html__( 'Note: "Conditionally cached" means that the browser can re-validate the resource using ETag or Last-Modified headers. This results in fewer full downloads but still requires the browser to make requests, unlike far-future expiration headers that allow the browser to fully rely on its local cache for a longer duration.', 'performance-lab' ) ); } else { $result['actions'] = sprintf( @@ -84,11 +84,11 @@ function perflab_ffh_assets_test(): array { * @since n.e.x.t * * @param string[] $assets List of asset URLs to check. - * @return array{final_status: string, details: string[]} Final status and details. + * @return array{final_status: string, details: array{extension: string, reason: string}[]} Final status and details. */ function perflab_ffh_check_assets( array $assets ): array { - $final_status = 'good'; - $extension_results = array(); // Extensions that need improvement. + $final_status = 'good'; + $fail_details = array(); // Array of arrays with 'extension' and 'reason'. foreach ( $assets as $asset ) { $response = wp_remote_get( $asset, array( 'sslverify' => false ) ); @@ -98,30 +98,62 @@ function perflab_ffh_check_assets( array $assets ): array { $extension = isset( $path_info['extension'] ) ? strtolower( $path_info['extension'] ) : 'unknown'; if ( is_wp_error( $response ) ) { + // Can't determine headers if request failed, consider it a fail. + $final_status = 'recommended'; + $fail_details[] = array( + 'extension' => $extension, + 'reason' => __( 'Could not retrieve headers', 'performance-lab' ), + ); continue; } $headers = wp_remote_retrieve_headers( $response ); if ( ! is_object( $headers ) ) { + // No valid headers retrieved. + $final_status = 'recommended'; + $fail_details[] = array( + 'extension' => $extension, + 'reason' => __( 'No valid headers retrieved', 'performance-lab' ), + ); continue; } - if ( ! perflab_ffh_check_headers( $headers ) ) { - if ( ! perflab_ffh_try_conditional_request( $asset, $headers ) ) { - $final_status = 'recommended'; - $extension_results[] = $extension; - continue; - } + $check = perflab_ffh_check_headers( $headers ); + if ( isset( $check['passed'] ) && $check['passed'] ) { + // This asset passed far-future headers test, no action needed. + continue; + } - // Conditional pass means still recommended, not fully good. - $final_status = 'recommended'; - $extension_results[] = $extension; + // If not passed, decide whether to try conditional request. + if ( false === $check ) { + // Only if no far-future headers at all, we try conditional request. + $conditional_pass = perflab_ffh_try_conditional_request( $asset, $headers ); + if ( ! $conditional_pass ) { + $final_status = 'recommended'; + $fail_details[] = array( + 'extension' => $extension, + 'reason' => __( 'No far-future headers and no conditional caching', 'performance-lab' ), + ); + } else { + $final_status = 'recommended'; + $fail_details[] = array( + 'extension' => $extension, + 'reason' => __( 'No far-future headers but conditionally cached', 'performance-lab' ), + ); + } + } else { + // If there's a max-age or expires but below threshold, we skip conditional. + $final_status = 'recommended'; + $fail_details[] = array( + 'extension' => $extension, + 'reason' => $check['reason'], + ); } } return array( 'final_status' => $final_status, - 'details' => $extension_results, + 'details' => $fail_details, ); } @@ -131,9 +163,9 @@ function perflab_ffh_check_assets( array $assets ): array { * @since n.e.x.t * * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers Response headers. - * @return bool True if far-future headers are enabled, false otherwise. + * @return array{passed: bool, reason: string}|false Detailed result. If passed=false, reason explains why it failed and false if no headers found. */ -function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers ): bool { +function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers ) { /** * Filters the threshold for far-future headers. * @@ -161,19 +193,46 @@ function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictio // If max-age meets or exceeds the threshold, we consider it good. if ( $max_age >= $threshold ) { - return true; + return array( + 'passed' => true, + 'reason' => '', + ); } - // If max-age is not sufficient, check Expires. - // Expires is a date; we want to ensure it's far in the future. + // If max-age is too low or not present, check Expires. if ( is_string( $expires ) && '' !== $expires ) { $expires_time = strtotime( $expires ); if ( (bool) $expires_time && ( $expires_time - time() ) >= $threshold ) { - return true; + // Good - Expires far in the future. + return array( + 'passed' => true, + 'reason' => '', + ); + } + + // Expires header exists but not far enough in the future. + if ( $max_age > 0 && $max_age < $threshold ) { + return array( + 'passed' => false, + 'reason' => __( 'max-age below threshold', 'performance-lab' ), + ); } + return array( + 'passed' => false, + 'reason' => __( 'expires below threshold', 'performance-lab' ), + ); } - return false; + // No max-age or expires found at all or max-age < threshold and no expires. + if ( 0 === $max_age ) { + return false; + } else { + // max-age was present but below threshold and no expires. + return array( + 'passed' => false, + 'reason' => __( 'max-age below threshold', 'performance-lab' ), + ); + } } /** @@ -212,25 +271,25 @@ function perflab_ffh_try_conditional_request( string $url, WpOrg\Requests\Utilit } /** - * Generate a table listing file extensions that need far-future headers. + * Generate a table listing file extensions that need far-future headers, including reasons. * * @since n.e.x.t * - * @param string[] $extensions Array of file extensions needing improvement. + * @param array $fail_details Array of arrays with 'extension' and 'reason'. * @return string HTML formatted table. */ -function perflab_ffh_get_extensions_table( array $extensions ): string { +function perflab_ffh_get_extensions_table( array $fail_details ): string { $html_table = sprintf( '', esc_html__( 'File Extension', 'performance-lab' ), esc_html__( 'Status', 'performance-lab' ) ); - foreach ( $extensions as $extension ) { + foreach ( $fail_details as $detail ) { $html_table .= sprintf( '', - esc_html( $extension ), - esc_html__( 'Needs far-future headers', 'performance-lab' ) + esc_html( $detail['extension'] ), + esc_html( $detail['reason'] ) ); } From 655d4e58e2e647caa105d24b5e931a4ded172cb0 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 11 Dec 2024 23:56:08 +0530 Subject: [PATCH 08/54] Add test to ensure status is 'good' when all headers are valid --- .../test-far-future-headers.php | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php diff --git a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php new file mode 100644 index 0000000000..c49466ad3f --- /dev/null +++ b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php @@ -0,0 +1,81 @@ +> + */ + 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 when all assets have valid far-future headers, the status is "good". + */ + public function test_all_assets_valid_far_future_headers(): void { + // Mock responses: all assets have a max-age > 1 year (threshold). + $this->mocked_responses = array( + includes_url( 'js/wp-embed.min.js' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS + 1000 ) ) ), + includes_url( 'css/buttons.min.css' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS + 500 ) ) ), + includes_url( 'fonts/dashicons.woff2' ) => $this->build_response( 200, array( 'expires' => gmdate( 'D, d M Y H:i:s', time() + YEAR_IN_SECONDS + 1000 ) . ' GMT' ) ), + includes_url( 'images/media/video.png' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS + 2000 ) ) ), + ); + + $result = perflab_ffh_assets_test(); + $this->assertEquals( 'good', $result['status'] ); + $this->assertEmpty( $result['actions'] ); + } + + /** + * Mock HTTP requests for assets to simulate different responses. + * + * @param bool $response A preemptive return value of an HTTP request. Default false. + * @param array $args Request arguments. + * @param string $url The request URL. + * @return array Mocked response. + */ + public function mock_http_requests( bool $response, array $args, string $url ): array { + 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 $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 ), + ); + } +} From 20099bf04e6972833989108065742a83286a62c0 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Dec 2024 13:13:57 +0530 Subject: [PATCH 09/54] Add test for assets with conditional caching --- .../test-far-future-headers.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php index c49466ad3f..c33025d589 100644 --- a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php +++ b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php @@ -45,6 +45,36 @@ public function test_all_assets_valid_far_future_headers(): void { $this->assertEmpty( $result['actions'] ); } + /** + * Test that when an asset has no far-future headers but has conditional caching (ETag/Last-Modified), status is 'recommended'. + */ + public function test_assets_conditionally_cached(): void { + // For conditional caching scenario, setting etag/last-modified headers. + $this->mocked_responses = array( + 'js/wp-embed.min.js' => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS + 1000 ) ) ), + 'css/buttons.min.css' => $this->build_response( 200, array( 'etag' => '"123456789"' ) ), + 'fonts/dashicons.woff2' => $this->build_response( 200, array( 'last-modified' => gmdate( 'D, d M Y H:i:s', time() - 1000 ) . ' GMT' ) ), + 'images/media/video.png' => $this->build_response( + 200, + array( + 'etag' => '"123456789"', + 'last-modified' => gmdate( 'D, d M Y H:i:s', time() - 1000 ) . ' GMT', + ) + ), + ); + + // For the asset with just ETag/Last-Modified and no far-future headers, perflab_ffh_try_conditional_request will be attempted. + $this->mocked_responses['conditional_304'] = array( + 'response' => array( 'code' => 304 ), + 'headers' => array(), + 'body' => '', + ); + + $result = perflab_ffh_assets_test(); + $this->assertEquals( 'recommended', $result['status'] ); + $this->assertNotEmpty( $result['actions'] ); + } + /** * Mock HTTP requests for assets to simulate different responses. * @@ -54,6 +84,11 @@ public function test_all_assets_valid_far_future_headers(): void { * @return array Mocked response. */ public function mock_http_requests( bool $response, array $args, string $url ): array { + // If conditional headers used in second request, simulate a 304 response. + if ( isset( $this->mocked_responses['conditional_304'] ) && ( isset( $args['headers']['If-None-Match'] ) || isset( $args['headers']['If-Modified-Since'] ) ) ) { + return $this->mocked_responses['conditional_304']; + } + if ( isset( $this->mocked_responses[ $url ] ) ) { return $this->mocked_responses[ $url ]; } From 54f36214aa80edfea21c4f0800788a5f12d374bf Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Dec 2024 16:58:22 +0530 Subject: [PATCH 10/54] Add test for status messages based on far-future headers results --- .../test-far-future-headers.php | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php index c33025d589..cd89e36a58 100644 --- a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php +++ b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php @@ -61,13 +61,7 @@ public function test_assets_conditionally_cached(): void { 'last-modified' => gmdate( 'D, d M Y H:i:s', time() - 1000 ) . ' GMT', ) ), - ); - - // For the asset with just ETag/Last-Modified and no far-future headers, perflab_ffh_try_conditional_request will be attempted. - $this->mocked_responses['conditional_304'] = array( - 'response' => array( 'code' => 304 ), - 'headers' => array(), - 'body' => '', + 'conditional_304' => $this->build_response( 304 ), ); $result = perflab_ffh_assets_test(); @@ -75,6 +69,34 @@ public function test_assets_conditionally_cached(): void { $this->assertNotEmpty( $result['actions'] ); } + /** + * Test that different status messages are returned based on the test results. + */ + public function test_status_messages(): void { + $this->mocked_responses = array( + includes_url( 'js/wp-embed.min.js' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS - 1000 ) ) ), + includes_url( 'css/buttons.min.css' ) => $this->build_response( 200, array( 'expires' => gmdate( 'D, d M Y H:i:s', time() + YEAR_IN_SECONDS - 1000 ) . ' GMT' ) ), + includes_url( 'fonts/dashicons.woff2' ) => $this->build_response( 200, array( 'etag' => '"123456789"' ) ), + includes_url( 'images/media/video.png' ) => $this->build_response( 200, array() ), + 'conditional_304' => $this->build_response( 304 ), + ); + + $result = perflab_ffh_check_assets( + array( + includes_url( 'js/wp-embed.min.js' ), + includes_url( 'css/buttons.min.css' ), + includes_url( 'fonts/dashicons.woff2' ), + includes_url( 'images/media/video.png' ), + ) + ); + + $this->assertEquals( 'recommended', $result['final_status'] ); + $this->assertEquals( 'max-age below threshold', $result['details'][0]['reason'] ); + $this->assertEquals( 'expires below threshold', $result['details'][1]['reason'] ); + $this->assertEquals( 'No far-future headers but conditionally cached', $result['details'][2]['reason'] ); + $this->assertEquals( 'No far-future headers and no conditional caching', $result['details'][3]['reason'] ); + } + /** * Mock HTTP requests for assets to simulate different responses. * From 0a776c57e30adb76b46e53bd5c95454ccc4226e0 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Dec 2024 17:15:34 +0530 Subject: [PATCH 11/54] Add tests for filters affecting far-future headers asset checks --- .../test-far-future-headers.php | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php index cd89e36a58..89484158df 100644 --- a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php +++ b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php @@ -97,6 +97,49 @@ public function test_status_messages(): void { $this->assertEquals( 'No far-future headers and no conditional caching', $result['details'][3]['reason'] ); } + /** + * Test that the filter `perflab_ffh_assets_to_check` and `perflab_far_future_headers_threshold` are working as expected. + */ + public function test_filters(): void { + add_filter( + 'perflab_ffh_assets_to_check', + static function ( $assets ) { + $assets[] = includes_url( 'images/blank.gif' ); + return $assets; + } + ); + + add_filter( + 'perflab_far_future_headers_threshold', + static function () { + return 1000; + } + ); + + $this->mocked_responses = array( + includes_url( 'js/wp-embed.min.js' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . 1500 ) ), + includes_url( 'css/buttons.min.css' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . 500 ) ), + includes_url( 'fonts/dashicons.woff2' ) => $this->build_response( 200, array( 'expires' => gmdate( 'D, d M Y H:i:s', time() + 1500 ) . ' GMT' ) ), + includes_url( 'images/media/video.png' ) => $this->build_response( 200, array( 'expires' => gmdate( 'D, d M Y H:i:s', time() + 500 ) . ' GMT' ) ), + includes_url( 'images/blank.gif' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( 500 ) ) ), + ); + + $result = perflab_ffh_check_assets( + array( + includes_url( 'js/wp-embed.min.js' ), + includes_url( 'css/buttons.min.css' ), + includes_url( 'fonts/dashicons.woff2' ), + includes_url( 'images/media/video.png' ), + includes_url( 'images/blank.gif' ), + ) + ); + + $this->assertEquals( 'recommended', $result['final_status'] ); + $this->assertEquals( 'max-age below threshold', $result['details'][0]['reason'] ); + $this->assertEquals( 'expires below threshold', $result['details'][1]['reason'] ); + $this->assertEquals( 'max-age below threshold', $result['details'][2]['reason'] ); + } + /** * Mock HTTP requests for assets to simulate different responses. * From 3c9833739bb096d3f870f147d3a4eac299eda153 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 12 Dec 2024 17:33:39 +0530 Subject: [PATCH 12/54] Refactor to use filenames instead of extenstions in failure details --- .../site-health/far-future-headers/helper.php | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index ae226b608e..f3077de41b 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -84,25 +84,25 @@ function perflab_ffh_assets_test(): array { * @since n.e.x.t * * @param string[] $assets List of asset URLs to check. - * @return array{final_status: string, details: array{extension: string, reason: string}[]} Final status and details. + * @return array{final_status: string, details: array{filename: string, reason: string}[]} Final status and details. */ function perflab_ffh_check_assets( array $assets ): array { $final_status = 'good'; - $fail_details = array(); // Array of arrays with 'extension' and 'reason'. + $fail_details = array(); // Array of arrays with 'filename' and 'reason'. foreach ( $assets as $asset ) { $response = wp_remote_get( $asset, array( 'sslverify' => false ) ); - // Extract extension from the URL. + // Extract filename from the URL. $path_info = pathinfo( (string) wp_parse_url( $asset, PHP_URL_PATH ) ); - $extension = isset( $path_info['extension'] ) ? strtolower( $path_info['extension'] ) : 'unknown'; + $filename = isset( $path_info['basename'] ) ? $path_info['basename'] : basename( $asset ); if ( is_wp_error( $response ) ) { // Can't determine headers if request failed, consider it a fail. $final_status = 'recommended'; $fail_details[] = array( - 'extension' => $extension, - 'reason' => __( 'Could not retrieve headers', 'performance-lab' ), + 'filename' => $filename, + 'reason' => __( 'Could not retrieve headers', 'performance-lab' ), ); continue; } @@ -112,8 +112,8 @@ function perflab_ffh_check_assets( array $assets ): array { // No valid headers retrieved. $final_status = 'recommended'; $fail_details[] = array( - 'extension' => $extension, - 'reason' => __( 'No valid headers retrieved', 'performance-lab' ), + 'filename' => $filename, + 'reason' => __( 'No valid headers retrieved', 'performance-lab' ), ); continue; } @@ -131,22 +131,22 @@ function perflab_ffh_check_assets( array $assets ): array { if ( ! $conditional_pass ) { $final_status = 'recommended'; $fail_details[] = array( - 'extension' => $extension, - 'reason' => __( 'No far-future headers and no conditional caching', 'performance-lab' ), + 'filename' => $filename, + 'reason' => __( 'No far-future headers and no conditional caching', 'performance-lab' ), ); } else { $final_status = 'recommended'; $fail_details[] = array( - 'extension' => $extension, - 'reason' => __( 'No far-future headers but conditionally cached', 'performance-lab' ), + 'filename' => $filename, + 'reason' => __( 'No far-future headers but conditionally cached', 'performance-lab' ), ); } } else { // If there's a max-age or expires but below threshold, we skip conditional. $final_status = 'recommended'; $fail_details[] = array( - 'extension' => $extension, - 'reason' => $check['reason'], + 'filename' => $filename, + 'reason' => $check['reason'], ); } } @@ -181,7 +181,7 @@ function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictio // Check Cache-Control header for max-age. $max_age = 0; if ( '' !== $cache_control ) { - // Cache-Control can have multiple directives; we only care about max-age. + // There can be multiple cache-control headers, we only care about max-age. $controls = is_array( $cache_control ) ? $cache_control : array( $cache_control ); foreach ( $controls as $control ) { if ( (bool) preg_match( '/max-age\s*=\s*(\d+)/', $control, $matches ) ) { @@ -271,24 +271,24 @@ function perflab_ffh_try_conditional_request( string $url, WpOrg\Requests\Utilit } /** - * Generate a table listing file extensions that need far-future headers, including reasons. + * Generate a table listing files that need far-future headers, including reasons. * * @since n.e.x.t * - * @param array $fail_details Array of arrays with 'extension' and 'reason'. + * @param array $fail_details Array of arrays with 'filename' and 'reason'. * @return string HTML formatted table. */ function perflab_ffh_get_extensions_table( array $fail_details ): string { $html_table = sprintf( '
%s%s
%s%s
', - esc_html__( 'File Extension', 'performance-lab' ), + esc_html__( 'File', 'performance-lab' ), esc_html__( 'Status', 'performance-lab' ) ); foreach ( $fail_details as $detail ) { $html_table .= sprintf( '', - esc_html( $detail['extension'] ), + esc_html( $detail['filename'] ), esc_html( $detail['reason'] ) ); } From 7afd746c88906ea928af8f075411e9df3a175a39 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 8 Jan 2025 20:32:19 +0530 Subject: [PATCH 13/54] Filter assets to ensure only string URLs are checked --- .../includes/site-health/far-future-headers/helper.php | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index f3077de41b..02f23ae9aa 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -52,6 +52,7 @@ function perflab_ffh_assets_test(): array { * @param string[] $assets List of asset URLs to check. */ $assets = apply_filters( 'perflab_ffh_assets_to_check', $assets ); + $assets = array_filter( (array) $assets, 'is_string' ); // Check if far-future headers are enabled for all assets. $results = perflab_ffh_check_assets( $assets ); From 3fcd64172336a32f88f5a461448f84a93b890bba Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Wed, 8 Jan 2025 20:37:19 +0530 Subject: [PATCH 14/54] Update parameter type hints for far-future headers functions --- .../site-health/far-future-headers/helper.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 02f23ae9aa..025338a684 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -109,7 +109,7 @@ function perflab_ffh_check_assets( array $assets ): array { } $headers = wp_remote_retrieve_headers( $response ); - if ( ! is_object( $headers ) ) { + if ( ! is_object( $headers ) && 0 === count( $headers ) ) { // No valid headers retrieved. $final_status = 'recommended'; $fail_details[] = array( @@ -163,10 +163,10 @@ function perflab_ffh_check_assets( array $assets ): array { * * @since n.e.x.t * - * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers Response headers. + * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary|array> $headers Response headers. * @return array{passed: bool, reason: string}|false Detailed result. If passed=false, reason explains why it failed and false if no headers found. */ -function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers ) { +function perflab_ffh_check_headers( $headers ) { /** * Filters the threshold for far-future headers. * @@ -239,11 +239,13 @@ function perflab_ffh_check_headers( WpOrg\Requests\Utility\CaseInsensitiveDictio /** * Attempt a conditional request with ETag/Last-Modified. * - * @param string $url The asset URL. - * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers The initial response headers. + * @since n.e.x.t + * + * @param string $url The asset URL. + * @param WpOrg\Requests\Utility\CaseInsensitiveDictionary|array> $headers The initial response headers. * @return bool True if a 304 response was received. */ -function perflab_ffh_try_conditional_request( string $url, WpOrg\Requests\Utility\CaseInsensitiveDictionary $headers ): bool { +function perflab_ffh_try_conditional_request( string $url, $headers ): bool { $etag = isset( $headers['etag'] ) ? $headers['etag'] : ''; $last_modified = isset( $headers['last-modified'] ) ? $headers['last-modified'] : ''; From 6a2da4759f38839ef875fbde764bd9e81b2bff6e Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 9 Jan 2025 21:08:08 +0530 Subject: [PATCH 15/54] Include max-age, expires actual value and threshold value in error message --- .../site-health/far-future-headers/helper.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 025338a684..a7e47fc643 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -215,12 +215,22 @@ function perflab_ffh_check_headers( $headers ) { if ( $max_age > 0 && $max_age < $threshold ) { return array( 'passed' => false, - 'reason' => __( 'max-age below threshold', 'performance-lab' ), + 'reason' => sprintf( + /* translators: 1: actual max-age value in seconds, 2: threshold in seconds */ + __( 'max-age below threshold (actual: %1$s seconds, threshold: %2$s seconds)', 'performance-lab' ), + $max_age, + $threshold + ), ); } return array( 'passed' => false, - 'reason' => __( 'expires below threshold', 'performance-lab' ), + 'reason' => sprintf( + /* translators: 1: actual Expires header value, 2: threshold in seconds */ + __( 'expires below threshold (actual: %1$s, threshold: %2$s seconds)', 'performance-lab' ), + $expires, + $threshold + ), ); } @@ -231,7 +241,12 @@ function perflab_ffh_check_headers( $headers ) { // max-age was present but below threshold and no expires. return array( 'passed' => false, - 'reason' => __( 'max-age below threshold', 'performance-lab' ), + 'reason' => sprintf( + /* translators: 1: actual max-age value in seconds, 2: threshold in seconds */ + __( 'max-age below threshold (actual: %1$s seconds, threshold: %2$s seconds)', 'performance-lab' ), + $max_age, + $threshold + ), ); } } From 48166ac4d7d1087e73d0368989d419f187c9195b Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 9 Jan 2025 21:15:07 +0530 Subject: [PATCH 16/54] Simplify filename extraction and conditional logic --- .../includes/site-health/far-future-headers/helper.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index a7e47fc643..39fa01340c 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -96,7 +96,7 @@ function perflab_ffh_check_assets( array $assets ): array { // Extract filename from the URL. $path_info = pathinfo( (string) wp_parse_url( $asset, PHP_URL_PATH ) ); - $filename = isset( $path_info['basename'] ) ? $path_info['basename'] : basename( $asset ); + $filename = $path_info['basename'] ?? basename( $asset ); if ( is_wp_error( $response ) ) { // Can't determine headers if request failed, consider it a fail. @@ -129,14 +129,13 @@ function perflab_ffh_check_assets( array $assets ): array { if ( false === $check ) { // Only if no far-future headers at all, we try conditional request. $conditional_pass = perflab_ffh_try_conditional_request( $asset, $headers ); + $final_status = 'recommended'; if ( ! $conditional_pass ) { - $final_status = 'recommended'; $fail_details[] = array( 'filename' => $filename, 'reason' => __( 'No far-future headers and no conditional caching', 'performance-lab' ), ); } else { - $final_status = 'recommended'; $fail_details[] = array( 'filename' => $filename, 'reason' => __( 'No far-future headers but conditionally cached', 'performance-lab' ), From 17d380e117179f2e2800c67a6157807cf2d8e200 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 9 Jan 2025 21:33:30 +0530 Subject: [PATCH 17/54] Refactor header checks to use null coalescing and improve conditionals --- .../site-health/far-future-headers/helper.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 39fa01340c..37dc60f8f3 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -175,16 +175,15 @@ function perflab_ffh_check_headers( $headers ) { */ $threshold = apply_filters( 'perflab_far_future_headers_threshold', YEAR_IN_SECONDS ); - $cache_control = isset( $headers['cache-control'] ) ? $headers['cache-control'] : ''; - $expires = isset( $headers['expires'] ) ? $headers['expires'] : ''; + $cache_control = $headers['cache-control'] ?? ''; + $expires = $headers['expires'] ?? ''; // Check Cache-Control header for max-age. $max_age = 0; if ( '' !== $cache_control ) { // There can be multiple cache-control headers, we only care about max-age. - $controls = is_array( $cache_control ) ? $cache_control : array( $cache_control ); - foreach ( $controls as $control ) { - if ( (bool) preg_match( '/max-age\s*=\s*(\d+)/', $control, $matches ) ) { + foreach ( (array) $cache_control as $control ) { + if ( 1 === preg_match( '/max-age\s*=\s*(\d+)/', $control, $matches ) ) { $max_age = (int) $matches[1]; break; } @@ -202,7 +201,7 @@ function perflab_ffh_check_headers( $headers ) { // If max-age is too low or not present, check Expires. if ( is_string( $expires ) && '' !== $expires ) { $expires_time = strtotime( $expires ); - if ( (bool) $expires_time && ( $expires_time - time() ) >= $threshold ) { + if ( is_int( $expires_time ) && ( $expires_time - time() ) >= $threshold ) { // Good - Expires far in the future. return array( 'passed' => true, @@ -211,7 +210,7 @@ function perflab_ffh_check_headers( $headers ) { } // Expires header exists but not far enough in the future. - if ( $max_age > 0 && $max_age < $threshold ) { + if ( $max_age > 0 ) { return array( 'passed' => false, 'reason' => sprintf( From 200b518e47a241b79c9f32fef1c160ad0a855e53 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 9 Jan 2025 21:54:11 +0530 Subject: [PATCH 18/54] Fix far future headers tests failing due to updated error messages --- .../far-future-headers/test-far-future-headers.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php index 89484158df..593f8d8f59 100644 --- a/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php +++ b/plugins/performance-lab/tests/includes/site-health/far-future-headers/test-far-future-headers.php @@ -91,8 +91,8 @@ public function test_status_messages(): void { ); $this->assertEquals( 'recommended', $result['final_status'] ); - $this->assertEquals( 'max-age below threshold', $result['details'][0]['reason'] ); - $this->assertEquals( 'expires below threshold', $result['details'][1]['reason'] ); + $this->assertStringContainsString( 'max-age below threshold (actual:', $result['details'][0]['reason'] ); + $this->assertStringContainsString( 'expires below threshold (actual:', $result['details'][1]['reason'] ); $this->assertEquals( 'No far-future headers but conditionally cached', $result['details'][2]['reason'] ); $this->assertEquals( 'No far-future headers and no conditional caching', $result['details'][3]['reason'] ); } @@ -135,9 +135,9 @@ static function () { ); $this->assertEquals( 'recommended', $result['final_status'] ); - $this->assertEquals( 'max-age below threshold', $result['details'][0]['reason'] ); - $this->assertEquals( 'expires below threshold', $result['details'][1]['reason'] ); - $this->assertEquals( 'max-age below threshold', $result['details'][2]['reason'] ); + $this->assertStringContainsString( 'max-age below threshold (actual:', $result['details'][0]['reason'] ); + $this->assertStringContainsString( 'expires below threshold (actual:', $result['details'][1]['reason'] ); + $this->assertStringContainsString( 'max-age below threshold (actual:', $result['details'][2]['reason'] ); } /** From 1b4cef4fd9f410e019f1da5dbfb012ca518cb7a4 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Fri, 10 Jan 2025 23:00:07 +0530 Subject: [PATCH 19/54] Improve error messages for far future headers check --- .../site-health/far-future-headers/helper.php | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php index 37dc60f8f3..8d3dc27c7b 100644 --- a/plugins/performance-lab/includes/site-health/far-future-headers/helper.php +++ b/plugins/performance-lab/includes/site-health/far-future-headers/helper.php @@ -200,8 +200,9 @@ function perflab_ffh_check_headers( $headers ) { // If max-age is too low or not present, check Expires. if ( is_string( $expires ) && '' !== $expires ) { - $expires_time = strtotime( $expires ); - if ( is_int( $expires_time ) && ( $expires_time - time() ) >= $threshold ) { + $expires_time = strtotime( $expires ); + $remaining_time = is_int( $expires_time ) ? $expires_time - time() : 0; + if ( $remaining_time >= $threshold ) { // Good - Expires far in the future. return array( 'passed' => true, @@ -216,18 +217,18 @@ function perflab_ffh_check_headers( $headers ) { 'reason' => sprintf( /* translators: 1: actual max-age value in seconds, 2: threshold in seconds */ __( 'max-age below threshold (actual: %1$s seconds, threshold: %2$s seconds)', 'performance-lab' ), - $max_age, - $threshold + number_format_i18n( $max_age ), + number_format_i18n( $threshold ) ), ); } return array( 'passed' => false, 'reason' => sprintf( - /* translators: 1: actual Expires header value, 2: threshold in seconds */ - __( 'expires below threshold (actual: %1$s, threshold: %2$s seconds)', 'performance-lab' ), - $expires, - $threshold + /* translators: 1: actual Expires header value in seconds, 2: threshold in seconds */ + __( 'expires below threshold (actual: %1$s seconds, threshold: %2$s seconds)', 'performance-lab' ), + number_format_i18n( $remaining_time ), + number_format_i18n( $threshold ) ), ); } @@ -242,8 +243,8 @@ function perflab_ffh_check_headers( $headers ) { 'reason' => sprintf( /* translators: 1: actual max-age value in seconds, 2: threshold in seconds */ __( 'max-age below threshold (actual: %1$s seconds, threshold: %2$s seconds)', 'performance-lab' ), - $max_age, - $threshold + number_format_i18n( $max_age ), + number_format_i18n( $threshold ) ), ); } From 0eeb784e2e5bef760ee44e6b5e38bd2c0d2bd6dd Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Jan 2025 12:11:06 -0800 Subject: [PATCH 20/54] Disambiguate XPaths for children of BODY with id, class, or role attributes --- .../class-od-html-tag-processor.php | 62 +++++++++++++++---- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/plugins/optimization-detective/class-od-html-tag-processor.php b/plugins/optimization-detective/class-od-html-tag-processor.php index 70bf7bc7f6..1807b6ac36 100644 --- a/plugins/optimization-detective/class-od-html-tag-processor.php +++ b/plugins/optimization-detective/class-od-html-tag-processor.php @@ -123,7 +123,7 @@ final class OD_HTML_Tag_Processor extends WP_HTML_Tag_Processor { * @var string * @link https://github.com/WordPress/performance/issues/1787 */ - const XPATH_PATTERN = '^(/([a-zA-Z0-9:_-]+|\*\[\d+\]\[self::[a-zA-Z0-9:_-]+\]))+$'; + const XPATH_PATTERN = '^(/([a-zA-Z0-9:_-]+|\*\[\d+\]\[self::[a-zA-Z0-9:_-]+\])(\[@\w+.*?\])*)+$'; /** * Bookmark for the end of the HEAD. @@ -151,6 +151,14 @@ final class OD_HTML_Tag_Processor extends WP_HTML_Tag_Processor { */ private $open_stack_tags = array(); + /** + * Stack of the attributes for open tags. + * + * @since n.e.x.t + * @var array> + */ + private $open_stack_attributes = array(); + /** * Open stack indices. * @@ -168,7 +176,7 @@ final class OD_HTML_Tag_Processor extends WP_HTML_Tag_Processor { * populated back into `$this->open_stack_tags` and `$this->open_stack_indices`. * * @since 0.4.0 - * @var array + * @var array>, indices: int[]}> */ private $bookmarked_open_stacks = array(); @@ -291,8 +299,9 @@ public function next_token(): bool { $this->current_xpath = null; // Clear cache. ++$this->cursor_move_count; if ( ! parent::next_token() ) { - $this->open_stack_tags = array(); - $this->open_stack_indices = array(); + $this->open_stack_tags = array(); + $this->open_stack_attributes = array(); + $this->open_stack_indices = array(); // Mark that the end of the document was reached, meaning that get_modified_html() should now be able to append markup to the HEAD and the BODY. $this->reached_end_of_document = true; @@ -306,6 +315,7 @@ public function next_token(): bool { if ( $this->previous_tag_without_closer ) { array_pop( $this->open_stack_tags ); + array_pop( $this->open_stack_attributes ); } if ( ! $this->is_tag_closer() ) { @@ -317,6 +327,7 @@ public function next_token(): bool { $i = array_search( 'P', $this->open_stack_tags, true ); if ( false !== $i ) { array_splice( $this->open_stack_tags, (int) $i ); + array_splice( $this->open_stack_attributes, (int) $i ); array_splice( $this->open_stack_indices, count( $this->open_stack_tags ) + 1 ); } } @@ -324,6 +335,16 @@ public function next_token(): bool { $level = count( $this->open_stack_tags ); $this->open_stack_tags[] = $tag_name; + // Capture key attributes for each tag on the stack. This is used to further disambiguate XPaths, specifically for children of the BODY for now. + $attributes = array(); + foreach ( array( 'id', 'class', 'role' ) as $attribute_name ) { + $attribute_value = $this->get_attribute( $attribute_name ); + if ( null !== $attribute_value ) { + $attributes[ $attribute_name ] = $attribute_value; + } + } + $this->open_stack_attributes[] = $attributes; + if ( ! isset( $this->open_stack_indices[ $level ] ) ) { $this->open_stack_indices[ $level ] = 0; } else { @@ -347,6 +368,7 @@ public function next_token(): bool { } $popped_tag_name = array_pop( $this->open_stack_tags ); + array_pop( $this->open_stack_attributes ); if ( $popped_tag_name !== $tag_name ) { $this->warn( __METHOD__, @@ -464,8 +486,9 @@ public function get_current_depth(): int { public function seek( $bookmark_name ): bool { $result = parent::seek( $bookmark_name ); if ( $result ) { - $this->open_stack_tags = $this->bookmarked_open_stacks[ $bookmark_name ]['tags']; - $this->open_stack_indices = $this->bookmarked_open_stacks[ $bookmark_name ]['indices']; + $this->open_stack_tags = $this->bookmarked_open_stacks[ $bookmark_name ]['tags']; + $this->open_stack_attributes = $this->bookmarked_open_stacks[ $bookmark_name ]['attributes']; + $this->open_stack_indices = $this->bookmarked_open_stacks[ $bookmark_name ]['indices']; } return $result; } @@ -483,8 +506,9 @@ public function set_bookmark( $name ): bool { $result = parent::set_bookmark( $name ); if ( $result ) { $this->bookmarked_open_stacks[ $name ] = array( - 'tags' => $this->open_stack_tags, - 'indices' => $this->open_stack_indices, + 'tags' => $this->open_stack_tags, + 'attributes' => $this->open_stack_attributes, + 'indices' => $this->open_stack_indices, ); } return $result; @@ -520,11 +544,11 @@ public function release_bookmark( $name ): bool { * @since 0.4.0 * @since 0.9.0 Renamed from get_breadcrumbs() to get_indexed_breadcrumbs(). * - * @return Generator Breadcrumb. + * @return Generator}> Breadcrumb. */ private function get_indexed_breadcrumbs(): Generator { foreach ( $this->open_stack_tags as $i => $breadcrumb_tag_name ) { - yield array( $breadcrumb_tag_name, $this->open_stack_indices[ $i ] ); + yield array( $breadcrumb_tag_name, $this->open_stack_indices[ $i ], $this->open_stack_attributes[ $i ] ); } } @@ -574,9 +598,23 @@ private function is_foreign_element(): bool { public function get_xpath(): string { if ( null === $this->current_xpath ) { $this->current_xpath = ''; - foreach ( $this->get_indexed_breadcrumbs() as $i => list( $tag_name, $index ) ) { - if ( $i < 2 || ( 2 === $i && '/HTML/BODY' === $this->current_xpath ) ) { + foreach ( $this->get_indexed_breadcrumbs() as $i => list( $tag_name, $index, $attributes ) ) { + if ( $i < 2 ) { $this->current_xpath .= "/$tag_name"; + } elseif ( 2 === $i && '/HTML/BODY' === $this->current_xpath ) { + $segment = "/$tag_name"; + foreach ( $attributes as $attribute_name => $attribute_value ) { + if ( true === $attribute_value ) { + $segment .= sprintf( '[@%s]', $attribute_name ); + } else { + $segment .= sprintf( + "[@%s='%s']", + $attribute_name, + addcslashes( $attribute_value, '\'\\' ) // TODO: Verify escaping. + ); + } + } + $this->current_xpath .= $segment; } else { $this->current_xpath .= sprintf( '/*[%d][self::%s]', $index + 1, $tag_name ); } From 286d9cbc2cac6bb5cce031ee833955e9f3ec3fe9 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Jan 2025 12:11:48 -0800 Subject: [PATCH 21/54] Update tests to incorporate new XPaths --- .../all-embeds-inside-viewport/expected.html | 90 +- .../all-embeds-inside-viewport/set-up.php | 2 +- .../nested-figure-embed/expected.html | 25 +- .../expected.html | 12 +- .../expected.html | 9 +- .../expected.html | 17 +- .../expected.html | 17 +- .../expected.html | 10 +- .../expected.html | 23 +- .../expected.html | 23 +- .../expected.html | 12 +- .../expected.html | 11 +- .../expected.html | 14 +- .../expected.html | 14 +- .../expected.html | 9 +- .../expected.html | 10 +- .../too-many-bookmarks/expected.html | 2 +- .../set-up.php | 4 +- .../expected.html | 2 +- .../set-up.php | 2 +- .../set-up.php | 6 +- .../set-up.php | 2 +- .../set-up.php | 14 +- .../expected.html | 4 +- .../set-up.php | 4 +- .../set-up.php | 2 +- .../expected.html | 10 +- .../set-up.php | 10 +- .../expected.html | 4 +- .../set-up.php | 8 +- .../expected.html | 4 +- .../set-up.php | 16 +- .../expected.html | 4 +- .../set-up.php | 16 +- .../set-up.php | 2 +- .../expected.html | 2 +- .../set-up.php | 4 +- .../set-up.php | 8 +- .../img-non-native-lazy-loading/set-up.php | 2 +- .../expected.html | 10 +- .../set-up.php | 8 +- .../expected.html | 10 +- .../set-up.php | 8 +- .../set-up.php | 2 +- .../expected.html | 8 +- .../test-cases/no-url-metrics/expected.html | 4 +- .../expected.html | 4 +- .../set-up.php | 4 +- .../expected.html | 2 +- .../set-up.php | 2 +- .../set-up.php | 2 +- .../set-up.php | 2 +- .../set-up.php | 2 +- .../responsive-background-images/set-up.php | 2 +- .../expected.html | 2 +- .../set-up.php | 2 +- .../expected.html | 2 +- .../set-up.php | 2 +- .../expected.html | 2 +- .../set-up.php | 2 +- .../expected.html | 2 +- .../set-up.php | 2 +- .../expected.html | 4 +- .../set-up.php | 8 +- .../expected.html | 4 +- .../set-up.php | 12 +- .../expected.html | 4 +- .../set-up.php | 6 +- .../image-prioritizer/tests/test-helper.php | 12 +- .../test-cases/many-images/expected.html | 2000 ++++++++--------- .../test-cases/no-url-metrics/expected.html | 2 +- .../tests/test-cases/video/expected.html | 2 +- .../test-cases/xhtml-response/expected.html | 2 +- .../test-class-od-html-tag-processor.php | 266 +-- 74 files changed, 1372 insertions(+), 1466 deletions(-) diff --git a/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html index cb8b50e3c2..dcef822fc0 100644 --- a/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html @@ -3,58 +3,58 @@ ... @@ -83,14 +83,14 @@
-
+
-
+
@@ -98,49 +98,49 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/set-up.php b/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/set-up.php index 6ea4d7f7c6..c7094d0d23 100644 --- a/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/set-up.php +++ b/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/set-up.php @@ -12,7 +12,7 @@ $elements[] = array_merge( $element_data, array( - 'xpath' => "/HTML/BODY/DIV/*[{$i}][self::FIGURE]/*[1][self::DIV]", + 'xpath' => "/HTML/BODY/DIV[@class='wp-site-blocks']/*[{$i}][self::FIGURE]/*[1][self::DIV]", ) ); } diff --git a/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html b/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html index ba37caa5ca..4f0750df97 100644 --- a/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html @@ -2,32 +2,19 @@ ... - - - - +
-
-
- +
+
+
-
+

So I heard you like FIGURE?

- +
Tagline from Figurine embed.
diff --git a/plugins/embed-optimizer/tests/test-cases/single-spotify-embed-outside-viewport-with-subsequent-script/expected.html b/plugins/embed-optimizer/tests/test-cases/single-spotify-embed-outside-viewport-with-subsequent-script/expected.html index 1921c4b476..16f1817074 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-spotify-embed-outside-viewport-with-subsequent-script/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-spotify-embed-outside-viewport-with-subsequent-script/expected.html @@ -2,17 +2,11 @@ ... - - +
-
-
+
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport-without-resized-data/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport-without-resized-data/expected.html index e580503c37..a12e3ba205 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport-without-resized-data/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport-without-resized-data/expected.html @@ -2,17 +2,16 @@ ... - - - +
- +
- + + diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html index a416747251..a12e3ba205 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html @@ -2,23 +2,16 @@ ... - - - - +
-
+
- +
- + + diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html index 6c44678b80..a12e3ba205 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html @@ -2,23 +2,16 @@ ... - - - - +
-
+
- +
- + + diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html index d55d88ef89..a12e3ba205 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html @@ -2,16 +2,10 @@ ... - - +
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html index 24ec36b5bf..664283a381 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html @@ -2,26 +2,17 @@ ... - - - - - - +
-
-
- - +
+
+ +
- + + diff --git a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html index cf6d4a12e5..664283a381 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html @@ -2,26 +2,17 @@ ... - - - - - - +
-
-
- - +
+
+ +
- + + diff --git a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html index 7b706664c1..664283a381 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html @@ -2,17 +2,11 @@ ... - - +
-
-
+
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html index 400790d6ef..295671002f 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html @@ -2,16 +2,11 @@ ... - - - - +
-
-
+
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html index d159a1ddfd..92094c1fcf 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html @@ -2,20 +2,12 @@ ... - - - - +
-
+
- +
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html index e207715e3f..92094c1fcf 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html @@ -2,20 +2,12 @@ ... - - - - +
-
+
- +
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html index 7badfa2ad9..295671002f 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html @@ -2,14 +2,11 @@ ... - - +
-
-
+
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html index 58f90072c4..92094c1fcf 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html @@ -2,16 +2,10 @@ ... - - +
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/too-many-bookmarks/expected.html b/plugins/embed-optimizer/tests/test-cases/too-many-bookmarks/expected.html index 999512d0a5..6d13da4a2b 100644 --- a/plugins/embed-optimizer/tests/test-cases/too-many-bookmarks/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/too-many-bookmarks/expected.html @@ -6,7 +6,7 @@
-
+
diff --git a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php index 048869df90..5839f6eb3c 100644 --- a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-on-all-breakpoints-but-not-desktop-with-fully-populated-sample-data/set-up.php @@ -26,7 +26,7 @@ static function () use ( $breakpoint_max_widths ) { 'viewport_width' => $non_desktop_viewport_width, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, @@ -47,7 +47,7 @@ static function () use ( $breakpoint_max_widths ) { 'viewport_width' => 1000, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.3, ), diff --git a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/expected.html b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/expected.html index 4a2eaac7a3..e89bacdf9b 100644 --- a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/expected.html @@ -6,7 +6,7 @@

Pretend this is a super long paragraph that pushes the next div out of the initial viewport.

-
This is so background!
+
This is so background!
diff --git a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php index 6653f015f5..dc4dc10f49 100644 --- a/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/background-image-outside-viewport-with-desktop-metrics-missing/set-up.php @@ -24,7 +24,7 @@ static function () use ( $breakpoint_max_widths ) { 'viewport_width' => $non_desktop_viewport_width, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php index 3e76d4ce71..fe81ed23db 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-and-lazy-loaded-background-image-outside-viewport-with-fully-populated-sample-data/set-up.php @@ -10,18 +10,18 @@ $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::DIV]', 'isLCP' => true, ), array( - 'xpath' => '/HTML/BODY/DIV/*[3][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, 'boundingClientRect' => $outside_viewport_rect, ), array( - 'xpath' => '/HTML/BODY/DIV/*[4][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[4][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-with-fully-populated-sample-data/set-up.php index cb39822c31..6107e8cd82 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-background-image-with-fully-populated-sample-data/set-up.php @@ -3,7 +3,7 @@ $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::DIV]', 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php index ee940eae52..2bf089a7c4 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data/set-up.php @@ -17,41 +17,41 @@ 'viewport_width' => $viewport_width, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::DIV]/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::DIV]/*[1][self::IMG]', 'isLCP' => true, ), array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::DIV]/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::DIV]/*[2][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, // Subsequent carousel slide. ), array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::DIV]/*[3][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::DIV]/*[3][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, // Subsequent carousel slide. ), array( - 'xpath' => '/HTML/BODY/DIV/*[3][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0 === $i ? 0.5 : 0.0, // Make sure that the _max_ intersection ratio is considered. ), // All are outside all initial viewports. array( - 'xpath' => '/HTML/BODY/DIV/*[5][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[5][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, 'boundingClientRect' => $outside_viewport_rect, ), array( - 'xpath' => '/HTML/BODY/DIV/*[6][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[6][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, 'boundingClientRect' => $outside_viewport_rect, ), array( - 'xpath' => '/HTML/BODY/DIV/*[7][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[7][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/expected.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/expected.html index 51215d05b2..6a5d64be0e 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/expected.html @@ -6,8 +6,8 @@
- Foo - Bar + Foo + Bar
diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/set-up.php index 2666fb6ce2..16b9cc4417 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-fully-incomplete-sample-data/set-up.php @@ -12,11 +12,11 @@ 'viewport_width' => 1000, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => true, ), array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', 'isLCP' => false, ), ), diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-stale-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-stale-sample-data/set-up.php index 60f03a4d6e..b6f41c93b1 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-stale-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-stale-sample-data/set-up.php @@ -3,7 +3,7 @@ $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', // Note: This is intentionally not reflecting the IMG in the HTML below. + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', // Note: This is intentionally not reflecting the IMG in the HTML below. 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/expected.html b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/expected.html index 4e1b0de926..1c31d64e15 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/expected.html @@ -10,11 +10,11 @@
- Mobile Logo - Phablet Logo - Tablet Logo - Desktop Logo - Desktop Logo + Mobile Logo + Phablet Logo + Tablet Logo + Desktop Logo + Desktop Logo
diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/set-up.php b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/set-up.php index 4f33479ff6..c8f3258385 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints/set-up.php @@ -13,23 +13,23 @@ static function () use ( $breakpoint_max_widths ) { $elements = array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[3][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[4][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[4][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[5][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[5][self::IMG]', ), ); $elements[ $i ]['isLCP'] = true; diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/expected.html b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/expected.html index 075275898c..255e096a89 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/expected.html @@ -8,8 +8,8 @@
- Mobile Logo - Desktop Logo + Mobile Logo + Desktop Logo
diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/set-up.php b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/set-up.php index 3f0b67e8c7..de5207f4db 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-non-consecutive-viewport-groups-with-missing-data-for-middle-group/set-up.php @@ -8,12 +8,12 @@ 'elements' => array( array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'intersectionRatio' => 1.0, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', 'intersectionRatio' => 0.0, ), ), @@ -28,12 +28,12 @@ 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'intersectionRatio' => 0.0, ), array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', 'intersectionRatio' => 1.0, ), ), diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/expected.html b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/expected.html index cd3ac13937..f30af0fb86 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/expected.html @@ -6,9 +6,9 @@
- Mobile Logo + Mobile Logo

New paragraph since URL Metrics were captured!

- Desktop Logo + Desktop Logo
diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/set-up.php b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/set-up.php index 4b7db6cc53..6228509cdd 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints-and-one-is-stale/set-up.php @@ -15,11 +15,11 @@ static function () { 'elements' => array( array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) @@ -33,11 +33,11 @@ static function () { 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) @@ -51,11 +51,11 @@ static function () { 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) @@ -69,11 +69,11 @@ static function () { 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/expected.html b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/expected.html index cbd0491a98..f64ff1dc56 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/expected.html @@ -7,8 +7,8 @@
- Mobile Logo - Desktop Logo + Mobile Logo + Desktop Logo
diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/set-up.php b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/set-up.php index 9c12a4adf2..5853037f80 100644 --- a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-two-non-consecutive-breakpoints/set-up.php @@ -15,11 +15,11 @@ static function () { 'elements' => array( array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) @@ -33,11 +33,11 @@ static function () { 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) @@ -51,11 +51,11 @@ static function () { 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) @@ -69,11 +69,11 @@ static function () { 'elements' => array( array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', ), ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-already-on-common-lcp-image-with-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-already-on-common-lcp-image-with-fully-populated-sample-data/set-up.php index 47f81633e3..950f2c642d 100644 --- a/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-already-on-common-lcp-image-with-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-already-on-common-lcp-image-with-fully-populated-sample-data/set-up.php @@ -4,7 +4,7 @@ array( array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), ) ); diff --git a/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/expected.html b/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/expected.html index 1f46f34686..ba24685f4c 100644 --- a/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/expected.html @@ -7,7 +7,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/set-up.php b/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/set-up.php index dba2b3d3ea..52c292d656 100644 --- a/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/fetch-priority-high-on-lcp-image-common-on-mobile-and-desktop-with-url-metrics-missing-in-other-groups/set-up.php @@ -16,7 +16,7 @@ static function () use ( $breakpoint_max_widths ) { 'viewport_width' => 375, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => true, ), ), @@ -31,7 +31,7 @@ static function () use ( $breakpoint_max_widths ) { 'viewport_width' => 1000, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => true, ), ), diff --git a/plugins/image-prioritizer/tests/test-cases/images-located-above-or-along-initial-viewport/set-up.php b/plugins/image-prioritizer/tests/test-cases/images-located-above-or-along-initial-viewport/set-up.php index d8cadae667..4f7d08a6ab 100644 --- a/plugins/image-prioritizer/tests/test-cases/images-located-above-or-along-initial-viewport/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/images-located-above-or-along-initial-viewport/set-up.php @@ -33,28 +33,28 @@ 'viewport_width' => $viewport_width, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $above_viewport_rect, 'boundingClientRect' => $above_viewport_rect, ), array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $left_of_viewport_rect, 'boundingClientRect' => $left_of_viewport_rect, ), array( - 'xpath' => '/HTML/BODY/DIV/*[3][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $right_of_viewport_rect, 'boundingClientRect' => $right_of_viewport_rect, ), array( - 'xpath' => '/HTML/BODY/DIV/*[4][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[4][self::IMG]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $below_viewport_rect, diff --git a/plugins/image-prioritizer/tests/test-cases/img-non-native-lazy-loading/set-up.php b/plugins/image-prioritizer/tests/test-cases/img-non-native-lazy-loading/set-up.php index 88c966d7c7..b0bd77140d 100644 --- a/plugins/image-prioritizer/tests/test-cases/img-non-native-lazy-loading/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/img-non-native-lazy-loading/set-up.php @@ -10,7 +10,7 @@ 'viewport_width' => 1000, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', 'isLCP' => true, ), ), diff --git a/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/expected.html b/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/expected.html index cf7beb4298..6fcd36906c 100644 --- a/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/expected.html @@ -6,11 +6,11 @@
- - - - - + + + + +
diff --git a/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/set-up.php b/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/set-up.php index 245364eb4b..83100c805f 100644 --- a/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/multiple-videos-on-all-breakpoints/set-up.php @@ -18,25 +18,25 @@ static function () use ( $breakpoint_max_widths ) { 'elements' => array( array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 1.0, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 0.1, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[3][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 0.0, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[4][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[4][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 0.0, ), diff --git a/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/expected.html b/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/expected.html index d6b78a2d9d..c3ccef4936 100644 --- a/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/expected.html @@ -6,11 +6,11 @@
- - - - - + + + + +
diff --git a/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/set-up.php b/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/set-up.php index 65b95a21cd..606b43c883 100644 --- a/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/multiple-videos-with-desktop-metrics-missing/set-up.php @@ -18,25 +18,25 @@ static function () use ( $breakpoint_max_widths ) { 'elements' => array( array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 1.0, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[2][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 0.1, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[3][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[3][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 0.0, ), array( 'isLCP' => false, - 'xpath' => '/HTML/BODY/DIV/*[4][self::VIDEO]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[4][self::VIDEO]', 'boundingClientRect' => $test_case->get_sample_dom_rect(), 'intersectionRatio' => 0.0, ), diff --git a/plugins/image-prioritizer/tests/test-cases/no-lcp-image-or-background-image-outside-viewport-with-populated-url-metrics/set-up.php b/plugins/image-prioritizer/tests/test-cases/no-lcp-image-or-background-image-outside-viewport-with-populated-url-metrics/set-up.php index c0a5bf1735..145581fa70 100644 --- a/plugins/image-prioritizer/tests/test-cases/no-lcp-image-or-background-image-outside-viewport-with-populated-url-metrics/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/no-lcp-image-or-background-image-outside-viewport-with-populated-url-metrics/set-up.php @@ -3,7 +3,7 @@ $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::H1]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::H1]', 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/no-url-metrics-but-server-side-heuristics-added-fetchpriority-high/expected.html b/plugins/image-prioritizer/tests/test-cases/no-url-metrics-but-server-side-heuristics-added-fetchpriority-high/expected.html index f85d6cf747..5b735e433a 100644 --- a/plugins/image-prioritizer/tests/test-cases/no-url-metrics-but-server-side-heuristics-added-fetchpriority-high/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/no-url-metrics-but-server-side-heuristics-added-fetchpriority-high/expected.html @@ -5,10 +5,10 @@
- Foo - Bar - Baz - Qux + Foo + Bar + Baz + Qux
diff --git a/plugins/image-prioritizer/tests/test-cases/no-url-metrics/expected.html b/plugins/image-prioritizer/tests/test-cases/no-url-metrics/expected.html index b62547c485..e65e561ff0 100644 --- a/plugins/image-prioritizer/tests/test-cases/no-url-metrics/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/no-url-metrics/expected.html @@ -5,9 +5,9 @@
- Foo + Foo

Pretend this is a super long paragraph that pushes the next div out of the initial viewport.

-
This is so background!
+
This is so background!
diff --git a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html index 9d86483b02..1323295b46 100644 --- a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/expected.html @@ -23,14 +23,14 @@

Last Post

First Post

This post does have a featured image, and the server-side heuristics in WordPress cause it to get fetchpriority=high, but it should not have this since it is out of the viewport on mobile.

Pretend this is a super long paragraph that pushes the next div out of the initial viewport.

-
This is so background!
+
This is so background!
diff --git a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php index 29244432e3..72ff40f7e4 100644 --- a/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/only-mobile-and-desktop-groups-are-populated/set-up.php @@ -27,12 +27,12 @@ static function () { 'viewport_width' => $viewport_width, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::MAIN]/*[2][self::ARTICLE]/*[2][self::FIGURE]/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::MAIN]/*[2][self::ARTICLE]/*[2][self::FIGURE]/*[1][self::IMG]', 'isLCP' => $viewport_width > 600, 'intersectionRatio' => $viewport_width > 600 ? 1.0 : 0.1, ), array( - 'xpath' => '/HTML/BODY/DIV/*[2][self::MAIN]/*[4][self::DIV]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[2][self::MAIN]/*[4][self::DIV]', 'isLCP' => false, 'intersectionRatio' => 0.0, 'intersectionRect' => $outside_viewport_rect, diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html index 3c091020ba..d3119732ba 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/expected.html @@ -9,7 +9,7 @@ - Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/set-up.php b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/set-up.php index 414d90ee61..32acd76fde 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-as-lcp-tablet-and-desktop-metrics-missing/set-up.php @@ -22,7 +22,7 @@ static function () use ( $breakpoint_max_widths ) { 'viewport_width' => $viewport_width, 'elements' => array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::PICTURE]/*[3][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::PICTURE]/*[3][self::IMG]', 'isLCP' => true, ), ), diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/set-up.php b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/set-up.php index cb673b547d..2500623b0c 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-lcp-image-and-fully-populated-sample-data/set-up.php @@ -12,7 +12,7 @@ static function () use ( $breakpoint_max_widths ) { $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::PICTURE]/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::PICTURE]/*[2][self::IMG]', 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/set-up.php b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/set-up.php index cb673b547d..2500623b0c 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-having-media-attribute/set-up.php @@ -12,7 +12,7 @@ static function () use ( $breakpoint_max_widths ) { $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::PICTURE]/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::PICTURE]/*[2][self::IMG]', 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/set-up.php b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/set-up.php index cb673b547d..2500623b0c 100644 --- a/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/picture-element-with-source-missing-type-attribute/set-up.php @@ -12,7 +12,7 @@ static function () use ( $breakpoint_max_widths ) { $test_case->populate_url_metrics( array( array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::PICTURE]/*[2][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::PICTURE]/*[2][self::IMG]', 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/responsive-background-images/set-up.php b/plugins/image-prioritizer/tests/test-cases/responsive-background-images/set-up.php index f31160ed01..e79cafd611 100644 --- a/plugins/image-prioritizer/tests/test-cases/responsive-background-images/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/responsive-background-images/set-up.php @@ -26,7 +26,7 @@ static function () use ( $mobile_breakpoint, $tablet_breakpoint ): array { array( 'viewport_width' => $viewport_width, 'element' => array( - 'xpath' => sprintf( '/HTML/BODY/DIV/*[%d][self::DIV]', $div_index + 1 ), + 'xpath' => sprintf( '/HTML/BODY/DIV[@id=\'page\']/*[%d][self::DIV]', $div_index + 1 ), 'isLCP' => true, ), ) diff --git a/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/expected.html b/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/expected.html index d0dd1329d3..0e045721b8 100644 --- a/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/expected.html @@ -8,7 +8,7 @@
- Foo + Foo
diff --git a/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/set-up.php b/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/set-up.php index 568a112cd2..68190d2062 100644 --- a/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/set-up.php +++ b/plugins/image-prioritizer/tests/test-cases/url-metric-only-captured-for-one-breakpoint/set-up.php @@ -7,7 +7,7 @@ 'viewport_width' => 400, 'element' => array( 'isLCP' => true, - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', ), ) ) diff --git a/plugins/image-prioritizer/tests/test-cases/video-with-large-poster-and-desktop-url-metrics-collected/expected.html b/plugins/image-prioritizer/tests/test-cases/video-with-large-poster-and-desktop-url-metrics-collected/expected.html index 648472749f..659752173c 100644 --- a/plugins/image-prioritizer/tests/test-cases/video-with-large-poster-and-desktop-url-metrics-collected/expected.html +++ b/plugins/image-prioritizer/tests/test-cases/video-with-large-poster-and-desktop-url-metrics-collected/expected.html @@ -5,7 +5,7 @@
-
%s%s
%s%s
', esc_html__( 'File', 'performance-lab' ), diff --git a/plugins/performance-lab/includes/site-health/effective-asset-cache-headers/hooks.php b/plugins/performance-lab/includes/site-health/effective-asset-cache-headers/hooks.php index ede165b4bc..8faab27f2c 100644 --- a/plugins/performance-lab/includes/site-health/effective-asset-cache-headers/hooks.php +++ b/plugins/performance-lab/includes/site-health/effective-asset-cache-headers/hooks.php @@ -1,14 +1,16 @@ } $tests Site Health Tests. * @return array{direct: array} Amended tests. */ -function perflab_ffh_add_test( array $tests ): array { - $tests['direct']['far_future_headers'] = array( +function perflab_effective_asset_cache_headers_add_test( array $tests ): array { + $tests['direct']['effective_asset_cache_headers'] = array( 'label' => __( 'Effective Caching Headers', 'performance-lab' ), - 'test' => 'perflab_ffh_assets_test', + 'test' => 'perflab_effective_asset_cache_headers_assets_test', ); return $tests; } -add_filter( 'site_status_tests', 'perflab_ffh_add_test' ); +add_filter( 'site_status_tests', 'perflab_effective_asset_cache_headers_add_test' ); diff --git a/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php b/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php index 107a987520..a026caa552 100644 --- a/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php +++ b/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php @@ -1,12 +1,12 @@ array(), ); - $tests = perflab_ffh_add_test( $tests ); + $tests = perflab_effective_asset_cache_headers_add_test( $tests ); - $this->assertArrayHasKey( 'far_future_headers', $tests['direct'] ); - $this->assertEquals( 'Effective Caching Headers', $tests['direct']['far_future_headers']['label'] ); - $this->assertEquals( 'perflab_ffh_assets_test', $tests['direct']['far_future_headers']['test'] ); + $this->assertArrayHasKey( 'effective_asset_cache_headers', $tests['direct'] ); + $this->assertEquals( 'Effective Caching Headers', $tests['direct']['effective_asset_cache_headers']['label'] ); + $this->assertEquals( 'perflab_effective_asset_cache_headers_assets_test', $tests['direct']['effective_asset_cache_headers']['test'] ); } /** * Test that the far-future headers test is attached to the site status tests. * - * @covers ::perflab_ffh_add_test + * @covers ::perflab_effective_asset_cache_headers_add_test */ - public function test_perflab_ffh_add_test_is_attached_to_site_status_tests(): void { - $this->assertNotFalse( has_filter( 'site_status_tests', 'perflab_ffh_add_test' ) ); + public function test_perflab_effective_asset_cache_headers_add_test_is_attached_to_site_status_tests(): void { + $this->assertNotFalse( has_filter( 'site_status_tests', 'perflab_effective_asset_cache_headers_add_test' ) ); } /** * Test that when all assets have valid far-future headers, the status is "good". * - * @covers ::perflab_ffh_assets_test - * @covers ::perflab_ffh_check_assets - * @covers ::perflab_ffh_check_headers + * @covers ::perflab_effective_asset_cache_headers_assets_test + * @covers ::perflab_effective_asset_cache_headers_check_assets + * @covers ::perflab_effective_asset_cache_headers_check_headers */ - public function test_all_assets_valid_far_future_headers(): void { + public function test_all_assets_valid_effective_cache_headers(): void { // Mock responses: all assets have a max-age > 1 year (threshold). $this->mocked_responses = array( includes_url( 'js/wp-embed.min.js' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS + 1000 ) ) ), @@ -70,7 +70,7 @@ public function test_all_assets_valid_far_future_headers(): void { includes_url( 'images/media/video.png' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( YEAR_IN_SECONDS + 2000 ) ) ), ); - $result = perflab_ffh_assets_test(); + $result = perflab_effective_asset_cache_headers_assets_test(); $this->assertEquals( 'good', $result['status'] ); $this->assertEmpty( $result['actions'] ); } @@ -78,11 +78,11 @@ public function test_all_assets_valid_far_future_headers(): void { /** * Test that when an asset has no far-future headers but has conditional caching (ETag/Last-Modified), status is 'recommended'. * - * @covers ::perflab_ffh_assets_test - * @covers ::perflab_ffh_check_assets - * @covers ::perflab_ffh_check_headers - * @covers ::perflab_ffh_try_conditional_request - * @covers ::perflab_ffh_get_extensions_table + * @covers ::perflab_effective_asset_cache_headers_assets_test + * @covers ::perflab_effective_asset_cache_headers_check_assets + * @covers ::perflab_effective_asset_cache_headers_check_headers + * @covers ::perflab_effective_asset_cache_headers_try_conditional_request + * @covers ::perflab_effective_asset_cache_headers_get_status_table */ public function test_assets_conditionally_cached(): void { // For conditional caching scenario, setting etag/last-modified headers. @@ -100,7 +100,7 @@ public function test_assets_conditionally_cached(): void { 'conditional_304' => $this->build_response( 304 ), ); - $result = perflab_ffh_assets_test(); + $result = perflab_effective_asset_cache_headers_assets_test(); $this->assertEquals( 'recommended', $result['status'] ); $this->assertNotEmpty( $result['actions'] ); } @@ -109,7 +109,7 @@ public function test_assets_conditionally_cached(): void { * Test that ETag/Last-Modified is used for conditional requests. * * @dataProvider data_provider_conditional_headers - * @covers ::perflab_ffh_try_conditional_request + * @covers ::perflab_effective_asset_cache_headers_try_conditional_request * * @param string $url The URL to test. * @param array $headers The headers to send. @@ -121,7 +121,7 @@ public function test_try_conditional_request_function( string $url, array $heade $url => $response, ); - $result = perflab_ffh_try_conditional_request( $url, $headers ); + $result = perflab_effective_asset_cache_headers_try_conditional_request( $url, $headers ); $this->assertEquals( $expected, $result ); } @@ -169,9 +169,9 @@ public function data_provider_conditional_headers(): array { /** * Test that different status messages are returned based on the test results. * - * @covers ::perflab_ffh_check_assets - * @covers ::perflab_ffh_check_headers - * @covers ::perflab_ffh_try_conditional_request + * @covers ::perflab_effective_asset_cache_headers_check_assets + * @covers ::perflab_effective_asset_cache_headers_check_headers + * @covers ::perflab_effective_asset_cache_headers_try_conditional_request */ public function test_status_messages(): void { $this->mocked_responses = array( @@ -191,7 +191,7 @@ public function test_status_messages(): void { includes_url( 'images/media/code.png' ) => array(), ); - $result = perflab_ffh_check_assets( + $result = perflab_effective_asset_cache_headers_check_assets( array( includes_url( 'js/wp-embed.min.js' ), includes_url( 'css/buttons.min.css' ), @@ -207,8 +207,8 @@ public function test_status_messages(): void { $this->assertStringContainsString( 'max-age below threshold (actual:', $result['details'][0]['reason'] ); $this->assertStringContainsString( 'expires below threshold (actual:', $result['details'][1]['reason'] ); $this->assertStringContainsString( 'max-age below threshold (actual:', $result['details'][2]['reason'] ); - $this->assertEquals( 'No far-future headers but conditionally cached', $result['details'][3]['reason'] ); - $this->assertEquals( 'No far-future headers and no conditional caching', $result['details'][4]['reason'] ); + $this->assertEquals( 'No effective caching headers but conditionally cached', $result['details'][3]['reason'] ); + $this->assertEquals( 'No effective caching headers and no conditional caching', $result['details'][4]['reason'] ); $this->assertEquals( 'Could not retrieve headers', $result['details'][5]['reason'] ); $this->assertEquals( 'No valid headers retrieved', $result['details'][6]['reason'] ); } @@ -216,12 +216,12 @@ public function test_status_messages(): void { /** * Test that the filter `perflab_ffh_assets_to_check` and `perflab_far_future_headers_threshold` are working as expected. * - * @covers ::perflab_ffh_check_assets - * @covers ::perflab_ffh_check_headers + * @covers ::perflab_effective_asset_cache_headers_check_assets + * @covers ::perflab_effective_asset_cache_headers_check_headers */ public function test_filters(): void { add_filter( - 'perflab_ffh_assets_to_check', + 'perflab_effective_asset_cache_headers_assets_to_check', static function ( $assets ) { $assets[] = includes_url( 'images/blank.gif' ); return $assets; @@ -229,7 +229,7 @@ static function ( $assets ) { ); add_filter( - 'perflab_far_future_headers_threshold', + 'perflab_effective_asset_cache_headers_expiration_threshold', static function () { return 1000; } @@ -243,7 +243,7 @@ static function () { includes_url( 'images/blank.gif' ) => $this->build_response( 200, array( 'cache-control' => 'max-age=' . ( 500 ) ) ), ); - $result = perflab_ffh_check_assets( + $result = perflab_effective_asset_cache_headers_check_assets( array( includes_url( 'js/wp-embed.min.js' ), includes_url( 'css/buttons.min.css' ), @@ -262,12 +262,12 @@ static function () { /** * Test that when no assets are passed, the status is "good". * - * @covers ::perflab_ffh_check_assets + * @covers ::perflab_effective_asset_cache_headers_check_assets */ public function test_when_no_assets(): void { $this->mocked_responses = array(); - $result = perflab_ffh_check_assets( array() ); + $result = perflab_effective_asset_cache_headers_check_assets( array() ); $this->assertEquals( 'good', $result['final_status'] ); $this->assertEmpty( $result['details'] ); @@ -300,7 +300,7 @@ public function mock_http_requests( bool $response, array $args, string $url ) { * * @param int $status_code HTTP status code. * @param array $headers HTTP headers. - * @return array{response: array{code: int, message: string}, headers: WpOrg\Requests\Utility\CaseInsensitiveDictionary} + * @return array{response: array{code: int, message: string}, headers: WpOrg\Requests\Utility\CaseInsensitiveDictionary} Mock response. */ protected function build_response( int $status_code = 200, array $headers = array() ): array { return array( From a048e9f24ad4a3d6fa94967c9a51617dd8d87695 Mon Sep 17 00:00:00 2001 From: Aditya Dhade Date: Thu, 23 Jan 2025 01:40:29 +0530 Subject: [PATCH 46/54] Update comments to reflect effective asset cache headers in site health checks --- plugins/performance-lab/includes/site-health/load.php | 2 +- .../test-effective-asset-cache-headers.php | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/performance-lab/includes/site-health/load.php b/plugins/performance-lab/includes/site-health/load.php index 3ef6825492..3d726c00d7 100644 --- a/plugins/performance-lab/includes/site-health/load.php +++ b/plugins/performance-lab/includes/site-health/load.php @@ -32,6 +32,6 @@ require_once __DIR__ . '/avif-headers/helper.php'; require_once __DIR__ . '/avif-headers/hooks.php'; -// Far-Future Headers site health check. +// 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'; diff --git a/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php b/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php index a026caa552..2895027655 100644 --- a/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php +++ b/plugins/performance-lab/tests/includes/site-health/effective-asset-cache-headers/test-effective-asset-cache-headers.php @@ -29,7 +29,7 @@ public function setUp(): void { } /** - * Test that the far-future headers test is added to the site health tests. + * Test that the effective caching headers test is added to the site health tests. * * @covers ::perflab_effective_asset_cache_headers_add_test */ @@ -46,7 +46,7 @@ public function test_perflab_effective_asset_cache_headers_add_test(): void { } /** - * Test that the far-future headers test is attached to the site status tests. + * Test that the effective caching headers test is attached to the site status tests. * * @covers ::perflab_effective_asset_cache_headers_add_test */ @@ -55,7 +55,7 @@ public function test_perflab_effective_asset_cache_headers_add_test_is_attached_ } /** - * Test that when all assets have valid far-future headers, the status is "good". + * Test that when all assets have valid effective caching headers, the status is "good". * * @covers ::perflab_effective_asset_cache_headers_assets_test * @covers ::perflab_effective_asset_cache_headers_check_assets @@ -76,7 +76,7 @@ public function test_all_assets_valid_effective_cache_headers(): void { } /** - * Test that when an asset has no far-future headers but has conditional caching (ETag/Last-Modified), status is 'recommended'. + * Test that when an asset has no effective caching headers but has conditional caching (ETag/Last-Modified), status is 'recommended'. * * @covers ::perflab_effective_asset_cache_headers_assets_test * @covers ::perflab_effective_asset_cache_headers_check_assets From 94843c05f877f04eba2b38a7743b3ff9ad787c44 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 22 Jan 2025 12:49:57 -0800 Subject: [PATCH 47/54] Introduce a transitional XPath format so existing URL Metrics will not be invalidated --- .../buffer.html | 11 +++ .../expected.html | 13 ++++ .../set-up.php | 13 ++++ .../class-od-element.php | 73 ++++++++++++++++--- .../class-od-html-tag-processor.php | 38 +++++++++- .../optimization-detective/optimization.php | 4 +- .../tests/test-class-od-element.php | 54 +++++++++++++- .../test-class-od-html-tag-processor.php | 18 ++++- 8 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/buffer.html create mode 100644 plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/expected.html create mode 100644 plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/set-up.php diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/buffer.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/buffer.html new file mode 100644 index 0000000000..22ff70274f --- /dev/null +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/buffer.html @@ -0,0 +1,11 @@ + + + + ... + + +
+ Foo +
+ + diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/expected.html b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/expected.html new file mode 100644 index 0000000000..c924d465bf --- /dev/null +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/expected.html @@ -0,0 +1,13 @@ + + + + ... + + + +
+ Foo +
+ + + diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/set-up.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/set-up.php new file mode 100644 index 0000000000..71de532829 --- /dev/null +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-with-old-xpath-format/set-up.php @@ -0,0 +1,13 @@ +populate_url_metrics( + array( + array( + // Note: This is intentionally using old XPath scheme. This is to make sure that the old format still results in the expected optimization during a transitional period. + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::DIV]/*[1][self::IMG]', + 'isLCP' => true, + ), + ), + false + ); +}; diff --git a/plugins/optimization-detective/class-od-element.php b/plugins/optimization-detective/class-od-element.php index 0f672c218e..0c58e249d8 100644 --- a/plugins/optimization-detective/class-od-element.php +++ b/plugins/optimization-detective/class-od-element.php @@ -33,6 +33,14 @@ class OD_Element implements ArrayAccess, JsonSerializable { */ protected $data; + /** + * Transitional XPath. + * + * @since n.e.x.t + * @var non-empty-string|null + */ + protected $transitional_xpath = null; + /** * URL Metric that this element belongs to. * @@ -54,16 +62,6 @@ class OD_Element implements ArrayAccess, JsonSerializable { public function __construct( array $data, OD_URL_Metric $url_metric ) { $this->data = $data; - // Convert old XPath scheme. - $xpath = preg_replace( - '#^/\*\[1\]\[self::HTML\]/\*\[2\]\[self::BODY\]/\*\[\d+\]\[self::([a-zA-Z0-9:_-]+)\]#', - '/HTML/BODY/$1', - $this->data['xpath'] - ); - if ( is_string( $xpath ) && '' !== $xpath ) { - $this->data['xpath'] = $xpath; - } - $this->url_metric = $url_metric; } @@ -100,6 +98,9 @@ public function get_url_metric_group(): ?OD_URL_Metric_Group { * @return mixed|null The property value, or null if not set. */ public function get( string $key ) { + if ( 'xpath' === $key ) { + return $this->get_xpath(); + } return $this->data[ $key ] ?? null; } @@ -129,11 +130,58 @@ public function is_lcp_candidate(): bool { * Gets XPath for element. * * @since 0.7.0 + * @since n.e.x.t Returns the transitional XPath format. To access the underlying raw XPath, access the 'xpath' key of the jsonSerialize response. * * @return non-empty-string XPath. */ public function get_xpath(): string { - return $this->data['xpath']; + + if ( ! isset( $this->transitional_xpath ) ) { + $replacements = array( + + /* + * Convert the original XPath format for elements in the BODY. + * + * Example: + * /*[1][self::HTML]/*[2][self::BODY]/*[1][self::DIV]/*[1][self::IMG] + * => + * /HTML/BODY/DIV/*[1][self::IMG] + */ + '#^/\*\[1]\[self::HTML]/\*\[2]\[self::BODY]/\*\[\d+]\[self::([a-zA-Z0-9:_-]+)]#' => '/HTML/BODY/$1', + + /* + * Convert the original XPath format for elements in the HEAD. + * + * Example: + * /*[1][self::HTML]/*[1][self::HEAD]/*[1][self::META] + * => + * /HTML/HEAD/*[1][self::META] + */ + '#^/\*\[1\]\[self::HTML\]/\*\[1\]\[self::HEAD]#' => '/HTML/HEAD', + + /* + * Convert the new XPath format for elements in the BODY. + * + * Note that the new XPath format for elements in the HEAD does not need to be converted to the + * transitional format since disambiguating attributes are not used in the HEAD. + * + * Example: + * /HTML/BODY/DIV[@id='page']/*[1][self::IMG] + * => + * /HTML/BODY/DIV/*[1][self::IMG] + */ + '#^(/HTML/BODY/\w+)\[@[^\]]+?]#' => '$1', + ); + foreach ( $replacements as $search => $replace ) { + $xpath = preg_replace( $search, $replace, $this->data['xpath'], -1, $count ); + if ( $count > 0 ) { + $this->transitional_xpath = $xpath; + break; + } + } + } + + return $this->transitional_xpath ?? $this->data['xpath']; } /** @@ -200,6 +248,9 @@ public function offsetExists( $offset ): bool { */ #[ReturnTypeWillChange] public function offsetGet( $offset ) { + if ( 'xpath' === $offset ) { + return $this->get_xpath(); + } return $this->data[ $offset ] ?? null; } diff --git a/plugins/optimization-detective/class-od-html-tag-processor.php b/plugins/optimization-detective/class-od-html-tag-processor.php index 65558e0046..5b6902165c 100644 --- a/plugins/optimization-detective/class-od-html-tag-processor.php +++ b/plugins/optimization-detective/class-od-html-tag-processor.php @@ -195,6 +195,17 @@ final class OD_HTML_Tag_Processor extends WP_HTML_Tag_Processor { */ private $current_xpath = null; + /** + * Transitional XPath for the current tag. + * + * This is used to store the old XPath format in a transitional period until which new URL Metrics are expected to + * have been collected to purge out references to the old format. + * + * @since n.e.x.t + * @var string|null + */ + private $transitional_current_xpath = null; + /** * Whether the previous tag does not expect a closer. * @@ -300,7 +311,8 @@ public function expects_closer( ?string $tag_name = null ): bool { * @return bool Whether a token was parsed. */ public function next_token(): bool { - $this->current_xpath = null; // Clear cache. + $this->current_xpath = null; // Clear cache. + $this->transitional_current_xpath = null; // Clear cache. ++$this->cursor_move_count; if ( ! parent::next_token() ) { $this->open_stack_tags = array(); @@ -629,9 +641,31 @@ private function is_foreign_element(): bool { * * @since 0.4.0 * + * @param bool $transitional_format Whether to use the transitional XPath format. Default true. * @return string XPath. */ - public function get_xpath(): string { + public function get_xpath( bool $transitional_format = true ): string { + /* + * This transitional format is used by default for all extensions. The non-transitional format is used only in + * od_optimize_template_output_buffer() when setting the data-od-xpath attribute. This is so that the new format + * will replace the old format as new URL Metrics are collected. After a month of the new format being live, the + * transitional format can be eliminated. See the corresponding logic in OD_Element for normalizing both the + * old and new XPath formats to use the transitional format. + */ + if ( $transitional_format ) { + if ( null === $this->transitional_current_xpath ) { + $this->transitional_current_xpath = ''; + foreach ( $this->get_indexed_breadcrumbs() as $i => list( $tag_name, $index, $attributes ) ) { + if ( $i < 2 || ( 2 === $i && '/HTML/BODY' === $this->transitional_current_xpath ) ) { + $this->transitional_current_xpath .= "/$tag_name"; + } else { + $this->transitional_current_xpath .= sprintf( '/*[%d][self::%s]', $index + 1, $tag_name ); + } + } + } + return $this->transitional_current_xpath; + } + if ( null === $this->current_xpath ) { $this->current_xpath = ''; foreach ( $this->get_indexed_breadcrumbs() as $i => list( $tag_name, $index, $attributes ) ) { diff --git a/plugins/optimization-detective/optimization.php b/plugins/optimization-detective/optimization.php index 86b1993d33..5a2573c342 100644 --- a/plugins/optimization-detective/optimization.php +++ b/plugins/optimization-detective/optimization.php @@ -249,7 +249,9 @@ function od_optimize_template_output_buffer( string $buffer ): string { $processor->release_bookmark( $current_tag_bookmark ); if ( $tracked_in_url_metrics && $needs_detection ) { - $processor->set_meta_attribute( 'xpath', $processor->get_xpath() ); + // Note: This is explicitly using the non-transitional XPath format since this is what will get saved in newly-submitted URL Metrics. + $xpath = $processor->get_xpath( false ); + $processor->set_meta_attribute( 'xpath', $xpath ); } } while ( $processor->next_open_tag() ); diff --git a/plugins/optimization-detective/tests/test-class-od-element.php b/plugins/optimization-detective/tests/test-class-od-element.php index 02074c3827..16b51a0e03 100644 --- a/plugins/optimization-detective/tests/test-class-od-element.php +++ b/plugins/optimization-detective/tests/test-class-od-element.php @@ -39,7 +39,7 @@ static function ( array $schema ): array { ); $element_data = array( - 'xpath' => '/HTML/BODY/DIV/*[1][self::IMG]', + 'xpath' => '/HTML/BODY/HEADER/*[1][self::IMG]', 'isLCP' => false, 'isLCPCandidate' => true, 'intersectionRatio' => 0.123, @@ -148,4 +148,56 @@ static function ( array $schema ): array { } $this->assertInstanceOf( Exception::class, $exception ); // @phpstan-ignore method.impossibleType (It is thrown by offsetUnset actually.) } + + /** + * Data provider. + * + * @return array + */ + public function data_provider_test_transitional_get_xpath(): array { + return array( + 'current-without-disambiguating-attr-on-body-child' => array( + 'xpath' => '/HTML/BODY/HEADER/*[1][self::IMG]', + 'expected' => '/HTML/BODY/HEADER/*[1][self::IMG]', + ), + 'current-with-disambiguating-attr-on-body-child' => array( + 'xpath' => '/HTML/BODY/DIV[@id=\'page\']/*[1][self::IMG]', + 'expected' => '/HTML/BODY/DIV/*[1][self::IMG]', + ), + 'old-format-body' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::DIV]/*[1][self::IMG]', + 'expected' => '/HTML/BODY/DIV/*[1][self::IMG]', + ), + 'old-format-head' => array( + 'xpath' => '/*[1][self::HTML]/*[1][self::HEAD]/*[1][self::META]', + 'expected' => '/HTML/HEAD/*[1][self::META]', + ), + ); + } + + /** + * Test that get_xpath() converts to the transitional format. + * + * @dataProvider data_provider_test_transitional_get_xpath + * @covers ::get_xpath + */ + public function test_transitional_get_xpath( string $xpath, string $expected ): void { + + $element_data = array( + 'xpath' => $xpath, + 'isLCP' => false, + 'isLCPCandidate' => true, + 'intersectionRatio' => 0.123, + 'intersectionRect' => $this->get_sample_dom_rect(), + 'boundingClientRect' => $this->get_sample_dom_rect(), + ); + + $url_metric = $this->get_sample_url_metric( array( 'element' => $element_data ) ); + $element = $url_metric->get_elements()[0]; + + $this->assertSame( $expected, $element->get_xpath() ); + $this->assertSame( $expected, $element->get( 'xpath' ) ); + $this->assertSame( $expected, $element['xpath'] ); + $this->assertSame( $xpath, $url_metric->jsonSerialize()['elements'][0]['xpath'] ); + } } diff --git a/plugins/optimization-detective/tests/test-class-od-html-tag-processor.php b/plugins/optimization-detective/tests/test-class-od-html-tag-processor.php index 327d180c5a..adb3f768e6 100644 --- a/plugins/optimization-detective/tests/test-class-od-html-tag-processor.php +++ b/plugins/optimization-detective/tests/test-class-od-html-tag-processor.php @@ -433,16 +433,26 @@ public function data_provider_sample_documents(): array { */ public function test_next_tag_and_get_xpath( string $document, array $open_tags, array $xpath_breadcrumbs ): void { $p = new OD_HTML_Tag_Processor( $document ); - $this->assertSame( '', $p->get_xpath(), 'Expected empty XPath since iteration has not started.' ); + $this->assertSame( '', $p->get_xpath( false ), 'Expected empty XPath since iteration has not started.' ); $actual_open_tags = array(); $actual_xpath_breadcrumbs_mapping = array(); while ( $p->next_open_tag() ) { $actual_open_tags[] = $p->get_tag(); - $xpath = $p->get_xpath(); + $xpath = $p->get_xpath( false ); $this->assertArrayNotHasKey( $xpath, $actual_xpath_breadcrumbs_mapping, 'Each tag must have a unique XPath.' ); $actual_xpath_breadcrumbs_mapping[ $xpath ] = $p->get_breadcrumbs(); + + $transitional_xpath = $p->get_xpath( true ); + $this->assertRegExp( + '#^/HTML( + /HEAD(/\*\[\d+]\[self::\w+])? + | + /BODY(/DIV(/\*\[\d+]\[self::\w+])*)? + )?$#x', + $transitional_xpath + ); } $this->assertSame( $open_tags, $actual_open_tags, "Expected list of open tags to match.\nSnapshot: " . $this->export_array_snapshot( $actual_open_tags, true ) ); @@ -615,7 +625,7 @@ public function test_bookmarking_and_seeking(): void { $bookmarks[] = $bookmark; $actual_figure_contents[] = array( 'tag' => $processor->get_tag(), - 'xpath' => $processor->get_xpath(), + 'xpath' => $processor->get_xpath( false ), 'depth' => $processor->get_current_depth(), ); } @@ -656,7 +666,7 @@ public function test_bookmarking_and_seeking(): void { $processor->seek( $bookmark ); $sought_actual_contents[] = array( 'tag' => $processor->get_tag(), - 'xpath' => $processor->get_xpath(), + 'xpath' => $processor->get_xpath( false ), 'depth' => $processor->get_current_depth(), ); } From 468b6a75ae7ddd4260f51299de7f85e8f90d2fe6 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 22 Jan 2025 13:03:23 -0800 Subject: [PATCH 48/54] Update test snapshots for embed-optimizer due to XPath change --- .../all-embeds-inside-viewport/expected.html | 90 +++++++++---------- .../nested-figure-embed/expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 4 +- .../expected.html | 10 +-- .../expected.html | 10 +-- .../expected.html | 4 +- .../expected.html | 10 +-- 14 files changed, 104 insertions(+), 104 deletions(-) diff --git a/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html index dcef822fc0..cb8b50e3c2 100644 --- a/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/all-embeds-inside-viewport/expected.html @@ -3,58 +3,58 @@ ... @@ -83,14 +83,14 @@
-
+
-
+
@@ -98,49 +98,49 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html b/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html index 6bd7ae3183..4b08b0df4e 100644 --- a/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/nested-figure-embed/expected.html @@ -3,10 +3,10 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html index 11b8b714a3..a416747251 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-inside-viewport/expected.html @@ -3,17 +3,17 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html index 42c553dabc..6c44678b80 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport-on-mobile/expected.html @@ -3,17 +3,17 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html index 334cb77fad..d55d88ef89 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-twitter-embed-outside-viewport/expected.html @@ -3,15 +3,15 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html index 7c3eee88ff..d3009e5b11 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-inside-viewport/expected.html @@ -3,10 +3,10 @@ ... @@ -15,7 +15,7 @@
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html index 67a96c4452..3fd503ea94 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport-on-mobile/expected.html @@ -3,10 +3,10 @@ ... @@ -15,7 +15,7 @@
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html index 554fe5ae8f..453e6609c1 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-wordpress-tv-embed-outside-viewport/expected.html @@ -3,15 +3,15 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html index 9ee240bd6d..69aeac4693 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport-with-only-mobile-url-metrics/expected.html @@ -3,14 +3,14 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html index c4d507a946..d159a1ddfd 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-inside-viewport/expected.html @@ -3,17 +3,17 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html index ddd35463f2..e207715e3f 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-on-mobile/expected.html @@ -3,17 +3,17 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html index 6b79b28c7f..8d842390cd 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport-with-only-mobile-url-metrics/expected.html @@ -3,12 +3,12 @@ ...
-
+
diff --git a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html index 9e05ac2eaa..58f90072c4 100644 --- a/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html +++ b/plugins/embed-optimizer/tests/test-cases/single-youtube-embed-outside-viewport/expected.html @@ -3,15 +3,15 @@ ...
-
+
From be2086d7ab5b7ebf7ddced8c33fd945ec3510b46 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 22 Jan 2025 13:57:32 -0800 Subject: [PATCH 49/54] Improve test coverage of OD_URL_Metric and OD_Tag_Visitor_Registry --- .../test-class-od-tag-visitor-registry.php | 1 + .../tests/test-class-od-url-metric.php | 119 +++++++++++++++++- 2 files changed, 119 insertions(+), 1 deletion(-) diff --git a/plugins/optimization-detective/tests/test-class-od-tag-visitor-registry.php b/plugins/optimization-detective/tests/test-class-od-tag-visitor-registry.php index 7abd92b997..50742612fc 100644 --- a/plugins/optimization-detective/tests/test-class-od-tag-visitor-registry.php +++ b/plugins/optimization-detective/tests/test-class-od-tag-visitor-registry.php @@ -24,6 +24,7 @@ public function test(): void { // Add img visitor. $this->assertFalse( $registry->is_registered( 'img' ) ); + $this->assertNull( $registry->get_registered( 'img' ) ); $img_visitor = static function ( OD_Tag_Visitor_Context $context ) { return $context->processor->get_tag() === 'IMG'; }; diff --git a/plugins/optimization-detective/tests/test-class-od-url-metric.php b/plugins/optimization-detective/tests/test-class-od-url-metric.php index 19f6975396..c48e7caffa 100644 --- a/plugins/optimization-detective/tests/test-class-od-url-metric.php +++ b/plugins/optimization-detective/tests/test-class-od-url-metric.php @@ -368,6 +368,13 @@ public function data_provider_to_test_constructor_with_extended_schema(): array 'boundingClientRect' => $this->get_sample_dom_rect(), ); + $data = array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array( $valid_element ), + ); + return array( 'added_valid_root_property_populated' => array( 'set_up' => static function (): void { @@ -550,6 +557,112 @@ static function ( array $properties ): array { 'assert' => static function (): void {}, 'error' => 'OD_URL_Metric[elements][0][isColorful] is not of type boolean.', ), + + 'added_immutable_element_property' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['isLCP'] = array( + 'type' => 'string', + ); + return $properties; + } + ); + }, + 'data' => $data, + 'assert' => static function (): void {}, + 'error' => '', + 'wrong' => 'Filter: 'od_url_metric_schema_element_item_additional_properties'', + ), + + 'added_element_property_without_type' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['foo'] = array( + 'minimum' => 1, + ); + return $properties; + } + ); + }, + 'data' => $data, + 'assert' => function (): void { + $this->assertArrayHasKey( 'isLCP', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + $this->assertArrayNotHasKey( 'foo', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + }, + 'error' => '', + 'wrong' => 'Filter: 'od_url_metric_schema_element_item_additional_properties'', + ), + + 'added_element_property_with_invalid_type' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['foo'] = array( + 'type' => null, + ); + return $properties; + } + ); + }, + 'data' => $data, + 'assert' => function (): void { + $this->assertArrayHasKey( 'isLCP', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + $this->assertArrayNotHasKey( 'foo', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + }, + 'error' => '', + 'wrong' => 'Filter: 'od_url_metric_schema_element_item_additional_properties'', + ), + + 'added_element_property_with_required' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['foo'] = array( + 'type' => 'string', + 'required' => true, + ); + return $properties; + } + ); + }, + 'data' => $data, + 'assert' => function (): void { + $this->assertArrayHasKey( 'isLCP', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + $this->assertArrayHasKey( 'foo', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + $this->assertFalse( OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties']['foo']['required'] ); + }, + 'error' => '', + 'wrong' => 'Filter: 'od_url_metric_schema_element_item_additional_properties'', + ), + + 'added_element_property_invalid_default' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['foo'] = array( + 'type' => 'string', + 'default' => 'bard', + ); + return $properties; + } + ); + }, + 'data' => $data, + 'assert' => function (): void { + $this->assertArrayHasKey( 'isLCP', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + $this->assertArrayHasKey( 'foo', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties'] ); + $this->assertArrayNotHasKey( 'default', OD_URL_Metric::get_json_schema()['properties']['elements']['items']['properties']['foo'] ); + }, + 'error' => '', + 'wrong' => 'Filter: 'od_url_metric_schema_element_item_additional_properties'', + ), ); } @@ -565,12 +678,16 @@ static function ( array $properties ): array { * @param array $data Data. * @param Closure $assert Assert. * @param string $error Error. + * @param string $wrong Expected doing it wrong. */ - public function test_constructor_with_extended_schema( Closure $set_up, array $data, Closure $assert, string $error = '' ): void { + public function test_constructor_with_extended_schema( Closure $set_up, array $data, Closure $assert, string $error = '', string $wrong = '' ): void { if ( '' !== $error ) { $this->expectException( OD_Data_Validation_Exception::class ); $this->expectExceptionMessage( $error ); } + if ( '' !== $wrong ) { + $this->setExpectedIncorrectUsage( $wrong ); + } $url_metric_sans_extended_schema = new OD_URL_Metric( $data ); $set_up(); $url_metric_with_extended_schema = new OD_URL_Metric( $data ); From c259b5933b30d0ed07818c18742f592bc1cac7ac Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 22 Jan 2025 14:18:58 -0800 Subject: [PATCH 50/54] Improve docs on XPATH_PATTERN Co-authored-by: felixarntz --- .../class-od-html-tag-processor.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/optimization-detective/class-od-html-tag-processor.php b/plugins/optimization-detective/class-od-html-tag-processor.php index 5b6902165c..5f846eb52b 100644 --- a/plugins/optimization-detective/class-od-html-tag-processor.php +++ b/plugins/optimization-detective/class-od-html-tag-processor.php @@ -112,12 +112,13 @@ final class OD_HTML_Tag_Processor extends WP_HTML_Tag_Processor { * level, however, does introduce the risk of duplicate XPaths being computed. For example, if a theme has a * `
%s%s