VisualVerificationTest.java
package org.vaadin.addons.dramafinder.agent;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType.LaunchOptions;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Playwright;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.ExtendWith;
import org.vaadin.addons.dramafinder.AbstractBasePlaywrightIT;
/**
* Base class for temporary, agent-driven visual verification tests that run
* against an <strong>already running</strong> application.
* <p>
* Unlike the Spring-based integration test base, this class does not boot the
* application — keeping the app out of the test run is what makes iterations
* cheap. It launches a Playwright browser, opens a fresh {@link Page} per test
* at a 1920×1080 viewport, and wires in {@link AgentReporting} so a failure
* automatically produces a screenshot, semantic snapshot, and stack trace.
* <p>
* Typical use (a throwaway {@code AgentVerifyIT}):
* <pre>{@code
* class AgentVerifyIT extends VisualVerificationTest {
* @Test
* void verifyOrders() {
* open("orders");
* shot("01-order-list");
* GridElement.get(page).assertRowCount(12);
* }
* }
* }</pre>
* The base URL defaults to {@code http://localhost:8080} and can be overridden
* with the {@code dramafinder.agent.baseUrl} system property or the
* {@code DRAMAFINDER_BASE_URL} environment variable. Headless mode follows the
* same {@code headless} / {@code HEADLESS} convention as the rest of the
* library.
*/
@ExtendWith(AgentReporting.class)
public abstract class VisualVerificationTest implements AgentReportProvider {
private static final String DEFAULT_BASE_URL = "http://localhost:8080";
private static Playwright playwright;
private static Browser browser;
/** The active page for the current test. */
protected Page page;
/** The report writer for the current test. */
protected AgentReport report;
@BeforeAll
static void startBrowser() {
playwright = Playwright.create();
browser = playwright.chromium()
.launch(new LaunchOptions().setHeadless(isHeadless()));
}
@AfterAll
static void stopBrowser() {
if (browser != null) {
browser.close();
browser = null;
}
if (playwright != null) {
playwright.close();
playwright = null;
}
}
@BeforeEach
void openPage() {
page = browser.newPage();
page.setViewportSize(1920, 1080);
page.setDefaultTimeout(15000);
report = new AgentReport(page);
}
@AfterEach
void closePage() {
if (page != null) {
page.close();
page = null;
}
}
/**
* Navigate to a path relative to the configured base URL and wait for
* Vaadin to finish loading.
*
* @param path the route path (with or without a leading slash)
*/
protected void open(String path) {
String normalized = path.startsWith("/") ? path.substring(1) : path;
page.navigate(baseUrl() + "/" + normalized);
page.waitForFunction(AbstractBasePlaywrightIT.WAIT_FOR_VAADIN_SCRIPT);
}
/**
* Capture a numbered screenshot into the report directory.
*
* @param name a short descriptive name for the interaction point
*/
protected void shot(String name) {
report.shot(name);
}
/**
* The base URL the test connects to.
*
* @return the configured base URL (default {@code http://localhost:8080})
*/
protected String baseUrl() {
String property = System.getProperty("dramafinder.agent.baseUrl");
if (property != null && !property.isBlank()) {
return trimTrailingSlash(property);
}
String env = System.getenv("DRAMAFINDER_BASE_URL");
if (env != null && !env.isBlank()) {
return trimTrailingSlash(env);
}
return DEFAULT_BASE_URL;
}
@Override
public Page agentPage() {
return page;
}
@Override
public AgentReport agentReport() {
return report;
}
private static String trimTrailingSlash(String url) {
return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
}
private static boolean isHeadless() {
String value = System.getProperty("headless");
if (value == null || value.isBlank()) {
value = System.getenv("HEADLESS");
}
if (value == null || value.isBlank()) {
return true;
}
return Boolean.parseBoolean(value);
}
}