PlaywrightPageNavigator.java
package net.sasasin.sreader.service.extraction.browser;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.LoadState;
import com.microsoft.playwright.options.WaitUntilState;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import net.sasasin.sreader.config.FeedReaderProperties;
/** Shared navigation, network-idle wait, and content capture for Playwright pages. */
final class PlaywrightPageNavigator {
private final FeedReaderProperties.Playwright settings;
PlaywrightPageNavigator(FeedReaderProperties.Playwright settings) {
this.settings = Objects.requireNonNull(settings, "settings must not be null");
}
void navigate(Page page, URI requestedUri) {
Objects.requireNonNull(page, "page must not be null");
Objects.requireNonNull(requestedUri, "requestedUri must not be null");
page.navigate(
requestedUri.toString(),
new Page.NavigateOptions()
.setWaitUntil(WaitUntilState.DOMCONTENTLOADED)
.setTimeout(settings.navigationTimeout().toMillis()));
}
void waitNetworkIdleBestEffort(Page page) {
Objects.requireNonNull(page, "page must not be null");
try {
page.waitForLoadState(
LoadState.NETWORKIDLE,
new Page.WaitForLoadStateOptions().setTimeout(settings.networkIdleTimeout().toMillis()));
} catch (RuntimeException e) {
// Long-polling pages may never become idle; rendered DOM is still usable.
}
}
RenderedPage capture(Page page, URI requestedUri) {
Objects.requireNonNull(page, "page must not be null");
Objects.requireNonNull(requestedUri, "requestedUri must not be null");
URI finalUri = resolveFinalUri(page.url(), requestedUri);
return new RenderedPage(finalUri, page.content());
}
URI resolveFinalUri(String pageUrl, URI requestedUri) {
Objects.requireNonNull(requestedUri, "requestedUri must not be null");
if (pageUrl == null || pageUrl.isBlank()) {
return requestedUri;
}
try {
return new URI(pageUrl);
} catch (URISyntaxException e) {
return requestedUri;
}
}
}