1+ package io .swaglabs .portal .qa .utils ;
2+
3+ import com .microsoft .playwright .Locator ;
4+ import com .microsoft .playwright .Page ;
5+ import io .restassured .RestAssured ;
6+ import io .restassured .response .Response ;
7+ import lombok .AccessLevel ;
8+ import lombok .NoArgsConstructor ;
9+ import lombok .extern .slf4j .Slf4j ;
10+ import org .apache .http .HttpStatus ;
11+
12+ import java .util .List ;
13+ import java .util .Objects ;
14+ import java .util .Set ;
15+ import java .util .concurrent .ExecutorService ;
16+ import java .util .concurrent .Executors ;
17+ import java .util .concurrent .Future ;
18+ import java .util .stream .Collectors ;
19+
20+ /**
21+ * Utility class to validate all <a href> links on a web page using Playwright and RestAssured.
22+ * Collects all anchor tags, resolves their full URLs, checks for broken links (4xx/5xx), and logs the results.
23+ * Usage:
24+ * BrokenLinkValidatorUtils.validateAllLinks(page, "MethodNameXYZ");
25+ */
26+ @ Slf4j
27+ @ NoArgsConstructor (access = AccessLevel .PRIVATE )
28+ public class BrokenLinkValidatorUtils {
29+
30+ /**
31+ * Extracts and validates all unique anchor tags (<a href>) on the page.
32+ *
33+ * @param page The Playwright page instance.
34+ * @param methodName Name of the test method (for logging context).
35+ */
36+ public static void validateAllLinks (Page page , String methodName ) {
37+ Objects .requireNonNull (page , "Playwright page cannot be null!" );
38+ Set <String > uniqueLinks = extractUniqueLinks (page );
39+ log .info ("Total unique links found in method [{}]: {}" , methodName , uniqueLinks .size ());
40+ ExecutorService executor = Executors .newFixedThreadPool (10 ); // Tune this based on infra
41+ List <Future <String >> futures = uniqueLinks .stream ()
42+ .map (link -> executor .submit (() -> {
43+ String resolvedUrl = resolveAbsoluteUrl (page .url (), link );
44+ try {
45+ Response response = RestAssured .given ()
46+ .relaxedHTTPSValidation ()
47+ .when ()
48+ .head (resolvedUrl );
49+ int statusCode = response .getStatusCode ();
50+ if (statusCode >= HttpStatus .SC_BAD_REQUEST ) {
51+ log .info ("BROKEN LINK: {} --> HTTP {}" , resolvedUrl , statusCode );
52+ return resolvedUrl + " --> HTTP " + statusCode ;
53+ } else {
54+ log .debug ("Valid Link: {} --> HTTP {}" , resolvedUrl , statusCode );
55+ return null ;
56+ }
57+ } catch (Exception e ) {
58+ log .info ("BROKEN LINK: {} --> Exception: {}" , resolvedUrl , e .getMessage ());
59+ return resolvedUrl + " --> Exception: " + e .getMessage ();
60+ }
61+ }))
62+ .toList ();
63+ List <String > brokenLinks = futures .stream ()
64+ .map (future -> {
65+ try {
66+ return future .get (); // blocking until complete
67+ } catch (Exception e ) {
68+ String errMsg = "Future execution failed --> " + e .getMessage ();
69+ log .info (errMsg );
70+ return errMsg ;
71+ }
72+ })
73+ .filter (Objects ::nonNull )
74+ .toList ();
75+ executor .shutdown ();
76+ if (brokenLinks .isEmpty ()) {
77+ log .info ("No broken links found on page: {}" , page .url ());
78+ } else {
79+ log .info ("Total broken links found on page [{}]: {}" , page .url (), brokenLinks .size ());
80+ brokenLinks .forEach (link -> log .info ("Broken --> {}" , link ));
81+ }
82+ }
83+
84+
85+ /**
86+ * Extracts all unique href values from <a> tags on the page.
87+ *
88+ * @param page Playwright Page
89+ * @return Set of unique link hrefs
90+ */
91+ private static Set <String > extractUniqueLinks (Page page ) {
92+ Locator anchors = page .locator ("a[href]" );
93+ return anchors .all ().stream ()
94+ .map (element -> element .getAttribute ("href" ))
95+ .filter (Objects ::nonNull )
96+ .map (String ::trim )
97+ .filter (href -> !href .isEmpty ())
98+ .filter (href -> !href .startsWith ("javascript:" ))
99+ .filter (href -> !href .startsWith ("#" ))
100+ .collect (Collectors .toSet ());
101+ }
102+
103+ /**
104+ * Resolves relative URLs against the base page URL.
105+ *
106+ * @param baseUrl The current page URL
107+ * @param hrefValue The raw href
108+ * @return Absolute URL
109+ */
110+ private static String resolveAbsoluteUrl (String baseUrl , String hrefValue ) {
111+ if (hrefValue .startsWith ("http" )) {
112+ return hrefValue ;
113+ }
114+ if (hrefValue .startsWith ("/" )) {
115+ return baseUrl .replaceAll ("(?<=https?://[^/]+).*" , "" ) + hrefValue ;
116+ }
117+ // Fallback for relative paths
118+ return baseUrl + "/" + hrefValue ;
119+ }
120+ }
0 commit comments