33 * This module contains Cucumber step definitions for validating links in WP Rocket settings UI.
44 *
55 * @requires {@link ../../common/custom-world }
6- * @requires {@link @playwright/test }
76 * @requires {@link @cucumber/cucumber }
7+ * @requires {@link ../../common/selectors }
88 */
99import { Then } from '@cucumber/cucumber' ;
1010import { ICustomWorld } from '../../common/custom-world' ;
11+ import { linkValidationSelectors } from '../../common/selectors' ;
1112
1213/**
13- * Executes the step to verify that WP Rocket settings links are not broken.
14+ * Step definition that verifies WP Rocket settings links are not broken by collecting all links from settings tabs
15+ * and checking their HTTP status codes. Fails on client errors (4xx) and logs warnings for server errors (5xx)
16+ * to avoid flakiness from transient backend issues.
1417 *
1518 * @function
1619 * @async
1720 * @param {ICustomWorld } this - The Cucumber world context for the current scenario.
18- * @return {Promise<void> } - A Promise that resolves when the check is completed .
21+ * @return {Promise<void> } - A Promise that resolves when all links have been validated .
1922 */
2023Then ( 'WP Rocket settings links are not broken' , async function ( this : ICustomWorld ) {
2124 const hrefs = new Set < string > ( ) ;
@@ -24,7 +27,7 @@ Then('WP Rocket settings links are not broken', async function (this: ICustomWor
2427 await this . utils . visitPage ( 'wp-admin/options-general.php?page=wprocket' ) ;
2528
2629 const collectLinks = async ( ) : Promise < void > => {
27- const links = await this . page . $$eval ( '#wpbody-content a[href]' , ( elements ) =>
30+ const links = await this . page . $$eval ( linkValidationSelectors . allLinksInContent , ( elements ) =>
2831 elements
2932 . map ( ( element ) => element . getAttribute ( 'href' ) )
3033 . filter ( ( href ) : href is string => Boolean ( href ) )
@@ -39,16 +42,19 @@ Then('WP Rocket settings links are not broken', async function (this: ICustomWor
3942 await collectLinks ( ) ;
4043
4144 // Collect tab links from within the WP Rocket settings content.
42- const tabHrefs = await this . page . $$eval ( '#wpbody-content a[href*="page=wprocket#"]' , ( links ) =>
45+ // Links are collected from each tab separately because different tabs may display different content
46+ // and therefore different links. This ensures comprehensive link coverage across all settings sections.
47+ const tabHrefs = await this . page . $$eval ( linkValidationSelectors . tabLinksInContent , ( links ) =>
4348 links
4449 . map ( ( link ) => link . getAttribute ( 'href' ) )
45- . filter ( Boolean )
50+ . filter ( ( href ) : href is string => Boolean ( href ) )
4651 ) ;
4752
4853 const tabUrls = Array . from ( new Set ( tabHrefs ) ) . map ( ( href ) => {
49- return new URL ( href as string , this . page . url ( ) ) . toString ( ) ;
54+ return new URL ( href , this . page . url ( ) ) . toString ( ) ;
5055 } ) ;
5156
57+ // Visit each tab and collect its links to ensure all links across all settings sections are validated
5258 for ( const tabUrl of tabUrls ) {
5359 await this . page . goto ( tabUrl ) ;
5460 await this . page . waitForLoadState ( 'load' ) ;
@@ -97,30 +103,33 @@ Then('WP Rocket settings links are not broken', async function (this: ICustomWor
97103 try {
98104 const response = await this . page . request . get ( url , { maxRedirects : 5 , timeout : 30000 } ) ;
99105
100- // Only fail on 404 - this indicates a truly broken link
101- if ( response . status ( ) === 404 ) {
102- throw new Error ( `Link is broken: ${ url } returned 404` ) ;
106+ const status = response . status ( ) ;
107+
108+ // Treat all client errors (4xx) as hard failures, as they usually indicate broken or unauthorized links.
109+ // This includes 401/403, which can reveal authentication/authorization problems, not just 404 "not found".
110+ if ( status >= 400 && status < 500 ) {
111+ throw new Error ( `Client error: ${ url } returned status ${ status } ` ) ;
103112 }
104113
105- // For other non-2xx/3xx status codes (like 5xx), log but don't fail
106- if ( response . status ( ) >= 400 ) {
114+ // For server errors ( 5xx), log but don't fail to avoid flakiness from transient backend issues.
115+ if ( status >= 500 ) {
107116 // eslint-disable-next-line no-console
108117 console . warn (
109- `Warning: ${ url } returned status ${ response . status ( ) } , but continuing test (only 404s fail) `
118+ `Warning: ${ url } returned server error status ${ status } , but continuing test to avoid flakiness. `
110119 ) ;
111120 }
112121 } catch ( error ) {
113122 const message = error instanceof Error ? error . message : String ( error ) ;
114123
115- // If this is our explicit 404 error, re-throw it to fail the test
116- if ( message . includes ( 'returned 404 ') ) {
124+ // If this is a client error (4xx) , re-throw it to fail the test.
125+ if ( message . startsWith ( 'Client error: ') ) {
117126 throw error ;
118127 }
119128
120- // For network errors (timeouts, connection issues, etc.), log but don't fail
129+ // For network errors (timeouts, connection issues, etc.), log but don't fail.
121130 // eslint-disable-next-line no-console
122131 console . warn (
123- `Warning: Network error while requesting ${ url } : ${ message } . Continuing test (only 404s fail) .`
132+ `Warning: Network or non-client error while requesting ${ url } : ${ message } . Continuing test.`
124133 ) ;
125134 }
126135 }
0 commit comments