Skip to content

Commit fc7d2b3

Browse files
Charankumar HCharankumar H
authored andcommitted
Added waits and implement BrokenLinkValidatorUtils.
1 parent 4b213a1 commit fc7d2b3

File tree

5 files changed

+172
-0
lines changed

5 files changed

+172
-0
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@
4444
<version>2.0.17</version>
4545
<scope>test</scope>
4646
</dependency>
47+
<dependency>
48+
<groupId>io.rest-assured</groupId>
49+
<artifactId>rest-assured</artifactId>
50+
<version>5.5.5</version>
51+
</dependency>
4752
</dependencies>
4853
<build>
4954
<plugins>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.swaglabs.portal.qa.waits;
2+
3+
public interface IPlaywrightWait {
4+
5+
void waitForElementToBeVisible(String locator);
6+
7+
void waitForElementToBeHidden(String locator);
8+
9+
void waitForElementToBeAttached(String locator);
10+
11+
void waitForElementToBeDetached(String locator);
12+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.swaglabs.portal.qa.waits;
2+
3+
import com.microsoft.playwright.Page;
4+
import com.microsoft.playwright.options.WaitForSelectorState;
5+
6+
public class PlaywrightWait implements IPlaywrightWait {
7+
8+
private final Page PAGE;
9+
10+
public PlaywrightWait(Page page) {
11+
this.PAGE = page;
12+
}
13+
14+
@Override
15+
public void waitForElementToBeVisible(String locator) {
16+
PAGE.waitForSelector(locator, new Page.WaitForSelectorOptions().setState(WaitForSelectorState.VISIBLE));
17+
}
18+
19+
@Override
20+
public void waitForElementToBeHidden(String locator) {
21+
PAGE.waitForSelector(locator, new Page.WaitForSelectorOptions().setState(WaitForSelectorState.HIDDEN));
22+
}
23+
24+
@Override
25+
public void waitForElementToBeAttached(String locator) {
26+
PAGE.waitForSelector(locator, new Page.WaitForSelectorOptions().setState(WaitForSelectorState.ATTACHED));
27+
}
28+
29+
@Override
30+
public void waitForElementToBeDetached(String locator) {
31+
PAGE.waitForSelector(locator, new Page.WaitForSelectorOptions().setState(WaitForSelectorState.DETACHED));
32+
}
33+
}

src/test/java/io/swaglabs/portal/qa/commons/WebBaseTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import io.swaglabs.portal.qa.cdp.CdpCommands;
88
import io.swaglabs.portal.qa.constants.WebPortalConstants;
99
import io.swaglabs.portal.qa.listeners.WebTestListeners;
10+
import io.swaglabs.portal.qa.utils.BrokenLinkValidatorUtils;
1011
import io.swaglabs.portal.qa.utils.CdpUtils;
1112
import io.swaglabs.portal.qa.utils.PerformanceUtils;
1213
import lombok.extern.slf4j.Slf4j;
@@ -53,6 +54,7 @@ public void init(Method method) {
5354
}
5455
PerformanceUtils.evaluatePageLoadTime(page.get(), method.getName());
5556
PerformanceUtils.evaluateDomContentLoadTime(page.get(), method.getName());
57+
BrokenLinkValidatorUtils.validateAllLinks(page.get(), method.getName());
5658
}
5759

5860
@AfterMethod(alwaysRun = true)

0 commit comments

Comments
 (0)